Bootstrap

2023初级程序员-最新面试题八股文(超详细)

1.面向对象和面向过程的区别

面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发

面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事务在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。但是性能上来说,比面向过程要低。

2.介绍下Java中的基本数据类型

byte、short、int、long、float、double、boolean、char

注意点:

1.int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。

2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看得见。虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。

3.标识符的命名规则

标识符的含义:是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。

命名规则:(硬性要求) 标识符可以包含英文字母,0-9的数字,$以及_标识符不能以数字开头,标识符不是关键字

命名规范:(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。 变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。 方法名规范:同变量名。

4.instanceof关键字的作用

instanceof严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:

boolean result = obj instanceof Class

其中obj为一个对象,Class表示一个类或者一个接口,当obj为Class的对象,或者是直接或间接子类,或者是其接口的实现类,结果result都返回true,否则返回false。

注意:编译器会检测obj是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过

Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true

//false ,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。
System.out.println(null instanceof Object);

5.重载和重写的区别

重写(Override)

从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。

子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,对方法体进行修改或重写,这就是重写。但是注意子类函数的访问修饰权限不能少于父类的。

public class Father {
 public static void main(String[] args) {
 // TODO Auto-generated method stub
 Son s = new Son();
 s.sayHello();
 }
 public void sayHello() {
 System.out.println("Hello");
 }
}
class Son extends Father{
 @Override
 public void sayHello() {
 // TODO Auto-generated method stub
 System.out.println("hello by ");
 }
}

重写总结:

1.发生在父类与子类之间

2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同

3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)

4.重写方法不一定能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载(Overload)

在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同但不能通过返回类型是否相同来判断重载。

public class Father {
 public static void main(String[] args) {
 // TODO Auto-generated method stub
 Father s = new Father();
 s.sayHello();
 s.sayHello("wintershii");
 }
 public void sayHello() {
 System.out.println("Hello");
 }
 public void sayHello(String name) {
 System.out.println("Hello" + " " + name);
 }
}

重载 总结:

1.重载Overload是一个类中多态性的一种表现

2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)

3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型作为重载函数的区分标准

6.介绍下内部类

目的:提高安全性

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这三种:成员内部类、局部内部类、匿名内部类,如下图所示:

 

7.介绍下java中的四种引用

强引用

强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:

String str  = new String("str");

System.out.println(str);

软引用

软引用在程序内存不足时,会被回收,使用方式:

//  注意: wrf 这个引用也是强引用,它是指向SoftReference这个对象的,

//  这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中

SoftReference<String> wrf = new SoftReference<String>(new String("str"));

可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。

弱引用

弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:

WeakReference<String> wrf = new WeakReference<String>(str);

可用场景: Java源码中的java.util.WeakHashMap 中的key 就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。

虚引用

虚引用的回收机制跟弱引用查不多,但是它被回收之前,会被放入ReferenceQueue中。注意哦,其它引用是被JVM回收后才被传入ReferenceQueue中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有ReferenceQueue,使用例子:

PhantomReference<String> prf = new PhantomReference<String>(new String("str"),new ReferenceQueue<>());

可用场景:对象销毁前的一些操作,比如说资源释放等。Object.finalize()虽然也可以做这类动作,但是这个方法即不安全又低效

上诉所说的几类引用,都是指对象本身的引用,而不是指Reference的四个子类的引用(SoftReference等)。

8.HashCode的作用

java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就会比较慢。于是有人发明了哈希算法来提高集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了,不相同就散列其他的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

9.有没有可能两个不相等的对象有相同的hashcode

能.在产生hash冲突时,两个不相等的对象就会有相同的hashcode值.当hash冲突产生时,一般有以下几种方式来处理:

  • 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.

  • 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入

  • 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个.....等哈希函数计算地址,直到无冲突

10.深拷贝和浅拷贝的区别是什么?

原型模式: 设计模式 --> Spring bean的Scope

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.

 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝要复制的对象引用的对象都复制了一遍.

 

11.static都有哪些用法?

所有的人都知道static关键字这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/

方法都属于类的静态资源,类实例所共享.

除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作;

public calss PreCache{

static{

	//执行相关操作

}

}

此外static也多用于修饰内部类,此时称之为静态内部类.

最后一种用法就是静态导包,即import static .import static是在JDK1.5之后引入的新特性,可以用来指定导入某个类的静态资源,并且不需要使用类名,可以直接使用资源名,比如:

import static java.lang.Math.*;
public class Test{
 public static void main(String[] args){
 //System.out.println(Math.sin(20));传统做法
 System.out.println(sin(20));
 }
}

12.介绍下Object中的常用方法

 

clone方法

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException 异常,深拷贝也需要实现Cloneable,同时其成员变量为引用类型的也需要实现Cloneable,然后重写clone方法。

finalize方法

该方法和垃圾收集器有关系,判断一个对象是否可以被收回的最好一步就是判断是否重写此方法。

equals方法

该方法使用频率非常高。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法

hashCode方法

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法,这个方法在一些具有哈希功能的Collection中用到。

一般必须满足 obj1.equals(obj2)==true,可以推出 obj1.hashCode() == obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

  • JDK1.6、1.7默认是返回随机数;

  • JDK1.8默认是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia‘s xorshift

    随机数算法得到的一个随机数。

wait方法

配合synchronized使用,wait方法就是当线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。 wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。 调用该方法后当前线程进入睡眠状态,直到以下事件发送。

  1. 其他线程调用了该对象的notify方法;

  2. 其他线程调用了该对象的notifyAll方法;

  3. 其他线程调用了interrupt中断该线程;

  4. 时间间隔到了。

    此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

notify方法

配合synchronized使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占CPU的线程,等待队列中的线程指的是等待唤醒的线程)。

notifyAll方法

配合synchronized使用,该方法唤醒在该对象上等待队列中的所有线程。

总结

只要把上面几个方法熟悉就可以了,toString和getClass方法可以不用去讨论它们。该题目考察的是对Object的熟悉程度,平时用的很多方法并没看其定义但是也在于用,比如说:wait()方法,equals()方法等。

Class Object is the root of the class hierarchy.Every class has Object as a
superclass. All objects, including arrays, implement the methods of this class.

大致意思: Object是所有类的根,是所有类的父类,所有对象包括数组都实现了Object的方法。

13.Java创建对象有几种方式?

new关键字

平时使用的最多的创建对象方式

User user = new User();

反射方式

使用newInstance(),但是得处理两个异常InstantiationException、iLLegalAccessException:

User user  = User.class.newInstance();
Object object=(Object)Class.forName("java.lang.Object").newInstance()

clone方法

Object对象中的clone方法来完成这个操作

反序列化操作

调用 ObjectInputStream类的readObject()方法。我们反序列化一个对象,JVM会给我们创建一个单独的对象。JVM创建对象并不会调用任何构造函数。一个对象实现了Serializable接口,就可以把对象写入到文中,并通过读取文件来创建对象。

总结

创建对象的方式关键字:new、反射、clone拷贝、反序列化。

14.有了数组为什么还要再搞一个ArrayList呢?

通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而ArrayList可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。

可以这么来理解:我们常说的数组是定死的数组,ArrayList却是动态数组。

15.说说什么是fail-fast?

fail-fast机制是Java集合(Collection) 中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。这里的操作主要是指add、remove和clear,对集合元素个数进行修改。

解决办法:建议使用"java.util.concurrent包下的的类"去取代java.util包下的类。可以这么理解:在遍历之前,把modCount记下来expectModCount,后面expectModCount去和modCount进行比较,如果不相等了,证明已经并发了,被修改了,于是抛出ConcurrentModificationException异常。

16.介绍下你对Java集合的理解

集合是我们在工作中使用频率非常高的组件了。下面的两张图是集合框架的类图结构。

 

17.介绍下你对红黑树的理解

红黑树的特点:

  

红黑色的本质: 2-3-4树

红黑树保证黑节点平衡的方式:左旋/右旋+变色 来保证

18.try-finally中的return关键字

return语句的本质:

1.return语句获取到变量的地址

2.return将获取的地址返回,也就是return本质是传地址

测试案例代码:

public class Demo02 {
    public static void main(String[] args) {
        Too too=new Too();
        StringBuilder t1=test(too);
        System.out.println("return语句返回的:"+t1+"\t返回值的hashCode:"+t1.hashCode());
        System.out.println("finaly里面修改的:"+too.num+"\tfinaly的hashCode:"+too.num.hashCode());

    }
    public static StringBuilder test(Too too) {
        try {
            too.num=new StringBuilder("try");
            System.out.println("try字符串的hashcode:"+("try").hashCode());
            System.out.println("StringBuilder里的try的hashCode:"+too.num.hashCode());//--语句1
            return too.num; //语句2
        } finally {
            too.num=new StringBuilder("finaly");//语句3
            System.out.println("finaly的hashCode:"+too.num.hashCode());//语句4
        }}}

class Too{
    StringBuilder num=new StringBuilder("你好");
}

输出结果:

try字符串的hashcode:115131

StringBuilder里的try的hashCode:460141958

finaly的hashCode:1163157884

return语句返回的:try  返回值的hashCode:460141958

finaly里面修改的:finaly	finaly的hashCode:1163157884

19.异常处理影响性能吗?

异常处理的性能成本非常高,每个Java程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。

仅在异常情况下使用异常;在可恢复的异常情况下使用异常;尽管使用异常有利于Java开发,但是应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。

20.介绍下try-with-resource语法

try-with-resources是JDK 7中一个新的异常处理机制,它能够很容易地关闭在try-catch语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources语句确保了每个资源在语句结束时关闭。所有实现了java.lang.AutoCloseable接口(其中,它包括实现了java.io.Closeable的所有对象),可以使用作为资源。

关闭单个资源:

public class Demo03 {
    public static void main(String[] args) {
        try(Resource res = new Resource()) {
            res.doSome();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

class Resource implements AutoCloseable {
    void doSome() {
        System.out.println("do something");
    }
    @Override
    public void close() throws Exception {
        System.out.println("resource is closed");
    }
}

查看编译后的代码

关闭多个资源

public class Demo04 {
    public static void main(String[] args) {
        try(ResourceSome some = new ResourceSome();
            ResourceOther other = new ResourceOther()) {
            some.doSome();
            other.doOther();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

class ResourceSome implements AutoCloseable {
    void doSome() {
        System.out.println("do something");
    }
    @Override
    public void close() throws Exception {
        System.out.println("some resource is closed");
    }
}

class ResourceOther implements AutoCloseable {
    void doOther() {
        System.out.println("do other things");
    }
    @Override
    public void close() throws Exception {
        System.out.println("other resource is closed");
    }
}

编译后的代码

 

处理规则

1.凡是实现了AutoCloseable接口的类,在try()里声明该实例的时候,在try结束后,close方法都会被调用

2.try结束后自动调用close方法,这个动作会早于finally里调用的方法。

3.不管是否出现异常(int i=1/0会抛出异常),try()里的实例都会被调用close方法

4.越晚声明的对象,会越早被close掉。

JDK9中的改进

在JDK9已得到改进。如果你已经有一个资源是final或等效于final变量,您可以try-with-resources语句中直接使用该变量,而无需在try-with-resources语句中声明一个新变量。

// A final resource
final Resource resource1 = new Resource("resource1");
// An effectively final resource
Resource resource2 = new Resource("resource2");
try (resource1;
     resource2) {
    // 直接使用 resource1 and resource 2.
}

21.介绍下进程和线程的关系

进程:一个独立的正在执行的程序

线程:一个进程的最基本的执行单位,执行路径

 

多进程:在操作系统中,同时运行多个程序

多进程的好处:可以充分利用CPU,提高CPU的使用率

多线程:在同一个进程(应用程序)中同时执行多个线程

多线程的好处:提高进程的执行使用率,提高了CPU的使用率

注意

  1. 在同一个时间点一个CPU中只可能有一个线程在执行

  2. 多线程不能提高效率、反而会降低效率,但是可以提高CPU的使用率

  3. 一个进程如果有多条执行路径,则称为多线程程序

  4. Java虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程

  5. 一个线程可以理解为进程的子任务

22.说说Java中实现多线程的几种方法

Thread对象就是一个线程

创建线程的常用三种方式:

  1. 继承Thread类

  2. 实现Runnable接口

  3. 实现Callable接口(JDK1.5>=)

  4. 线程池方式创建

    通过继承Thread类或者实现Runnable接口、Callable接口都可以实现多线程,不过实现Runnable

    接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方式返回值,可以声明抛出异常而已。因此将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。

继承Thread类

实现的步骤:

  1. 创建Thread类的子类

  2. 重写run方法

  3. 创建线程对象

  4. 启动线程

案例代码

package com.bobo.thread;

public class ThreadDemo02 {

    /**
     * 线程的第一种实现方式
     *     通过创建Thread类的子类来实现
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("main方法执行了1...");
        // Java中的线程 本质上就是一个Thread对象
        Thread t1 = new ThreadTest01();
        // 启动一个新的线程
        t1.start();
        for(int i = 0 ; i< 100 ; i++){
            System.out.println("main方法的循环..."+i);
        }
        System.out.println("main方法执行结束了3...");
    }

}

/**

 * 第一个自定义的线程类

 * 继承Thread父类

 * 重写run方法
    */
   class ThreadTest01 extends Thread{

   @Override
   public void run() {
       System.out.println("我们的第一个线程执行了2....");
       for(int i = 0 ; i < 10 ; i ++){
           System.out.println("子线程:"+i);
       }
   }
   }

注意点:

1.启动线程是使用start方法而不是run方法

2.线程不能启动多次,如果要创建多个线程,那么就需要创建多个Thread对象

 

实现Runnable接口

        在第一种实现方式中,我们是将线程的创建和线程执行的业务都封装在了Thread对象中,我们可以通过Runable接口来实现线程程序代码和数据有效的分离。

Thread(Runnable target)

//  分配一个新的  Thread对象。

实现的步骤:

  1. 创建Runable的实现类

  2. 重写run方法

  3. 创建Runable实例对象(通过实现类来实现)

  4. 创建Thread对象,并把第三部Runable实现作为Thread构造方法的参数

  5. 启动线程

package com.bobo.runable;

public class RunableDemo01 {

    /**
     * 线程的第二种方式
     *     本质是创建Thread对象的时候传递了一个Runable接口实现
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("main执行了...");
        // 创建一个新的线程  Thread对象
        Runnable r1 = new RunableTest();
        Thread t1 = new Thread(r1);
        // 启动线程
        t1.start();
        System.out.println("main结束了...");
    }

}

/**

 * 线程的第二种创建方式

 * 创建一个Runable接口的实现类
    */
   class RunableTest implements Runnable{

   @Override
   public void run() {
       System.out.println("子线程执行了...");
   }
   }

 

实现Runable接口的好处:

1.可以避免java单继承带来的局限性

2.适合多个相同的程序代码处理同一个资源的情况,把线程同程序的代码和数据有效的分离,较好的体现了面向对象的设计思想

Callable的方式

前面我们介绍的两种创建线程的方式都是重写run方法,而且run方法是没有返回结果的,也就是main方法是不知道开启的线程什么时候开始执行,什么时候结束执行,也获取不到对应的返回结果。而且run方法也不能把可能产生的异常抛出。在JDK1.5之后推出了通过实现Callable接口的方式来创建新的线程,这种方式可以获取对应的返回结果

package com.bobo.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo01 {

    /**
     * 创建线程的第三种实现方式:
     *    Callable方式
     */
    public static void main(String[] args) throws  Exception {
        // 创建一个Callable实例
        Callable<Integer> callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 获取一个线程 肯定是要先创建一个Thread对象  futureTask本质上是Runable接口的实现
        Thread t1 = new Thread(futureTask);
        System.out.println("main方法start....");
        t1.start(); // 本质还是执行的 Runable中的run方法,只是 run方法调用了call方法罢了
        // 获取返回的结果
        System.out.println(futureTask.get()); // 获取开启的线程执行完成后返回的结果
        System.out.println("main方法end ....");
    
    }

}

/**

 * 创建Callable的实现类

 * 我们需要指定Callable的泛型,这个泛型是返回结果的类型
    */
   class MyCallable implements Callable<Integer>{

   /**

    * 线程自动后会执行的方法
    * @return
    * @throws Exception
      */
      @Override
      public Integer call() throws Exception {
      int sum = 0;
      for(int i = 1 ; i <= 100 ; i ++){
          sum += i;
      }
      return sum;
    }
}

实现Runnable接口和实现Callable接口的区别:

  1. Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的

  2. Callable规定的方法是call(),而Runnable的任务是不能返回值(是void)

  3. Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)

  4. call方法可以抛出异常,run方法不可以

  5. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可以获取执行结果。

  6. 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。、

其实Callable接口底层的实现就是对Runable接口实现的封装,线程启动执行的也是Runable接口实现中的run方法,只是在run方法中有调用call方法罢了

23.如何停止一个正在运行的线程

设置标志位:如果线程的run方法中执行的是一个重复执行的循环,可以提供一个标记来控制循环是否继续

public class FunDemo02 {

    /**
     * 练习2:设计一个线程:运行10秒后被终止(掌握线程的终止方法)
     * @param args
     */
    public static void main(String[] args)  throws Exception{
        MyRunable02 runnable = new MyRunable02();
        new Thread(runnable).start();
        Thread.sleep(10000); // 主线程休眠10秒钟
        runnable.flag = false;
        System.out.println("main、  end ...");
    }

}

class MyRunable02 implements Runnable{

    boolean flag = true;
    
    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(1000);
                System.out.println(new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 执行完成");
    }

}

利用中断标志位:在线程中有个中断的标志位,默认是false,当我们显示的调用interrupted方法或者isInterrupted方法是会修改标志位为true。我们可以利用此来中断运行的线程。

package com.bobo.fundemo;

public class FunDemo07 extends Thread{

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new FunDemo07();
        t1.start();
        Thread.sleep(3000);
         t1.interrupt(); // 中断线程 将中断标志由false修改为了true
        // t1.stop(); // 直接就把线程给kill掉了
        System.out.println("main .... ");
    }
    
    @Override
    public void run() {
        System.out.println(this.getName() + " start...");
        int i = 0 ;
        // Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
        while(!Thread.interrupted()){
            System.out.println(this.getName() + " " + i);
            i++;
        }
    
        System.out.println(this.getName()+ " end .... ");
    
    }

}

利用InterruptedException:如果线程因为执行join(),sleep()或者wait()而进入阻塞状态,此时要想停止它,可以让他调用interrupt(),程序会抛出InterruptedException异常。我们利用这个异常可以来终止线程。

package com.bobo;

public class FunDemo08 extends Thread{

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new FunDemo08();
    t1.start();
    Thread.sleep(3000);
     t1.interrupt(); // 中断线程 将中断标志由false修改为了true
    // t1.stop(); // 直接就把线程给kill掉了
    System.out.println("main .... ");
}

@Override
public void run() {
    System.out.println(this.getName() + " start...");
    int i = 0 ;
    // Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
     while(!Thread.interrupted()){
    //while(!Thread.currentThread().isInterrupted()){
         try {
             Thread.sleep(10000);
         } catch (InterruptedException e) {
             e.printStackTrace();
			 break;
         }
         System.out.println(this.getName() + " " + i);
        i++;
    }

    System.out.println(this.getName()+ " end .... ");

	}
}

24.介绍下线程中的常用方法

1.start方法

start方法是我们开启一个新的线程的方法,但是并不是直接开启,而是告诉CPU我已经准备好了,快点运行我,这是启动一个线程的唯一入口。

void		start()

//导致此线程开始执行;Java虚拟机调用此线程的run方法。

2.run方法

线程的线程体,当一个线程开始运行后,执行的就是run方法里面的代码,我们不能直接通过线程对象

来调用run方法。因为这并没有产生一个新的线程。仅仅只是一个普通对象的方法调用。

3.getName方法

获取线程名称的方法

String getName()

返回此线程的名称

4.优先级

我们创建的多个线程的执行顺序是由CPU决定的。Java中提供了一个线程调度器来监控程序中启动后进入就绪状态的所有的线程,优先级高的线程会获取到比较多

**运行机会

**

    /**
     * 最小的优先级是 1
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * 默认的优先级都是5
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * 最大的优先级是10
     */
    public final static int MAX_PRIORITY = 10;

大家会发现,设置了优先级后输出的结果和我们预期的并不一样,这是为什么呢?优先级在CPU调动线程执行的时候会是一个参考因数,但不是决定因数,

5.sleep方法

将当前线程暂定指定时间

static void sleep(long millis)

//  使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

6.isAlive

获取线程的状态。

package com.bobo.fundemo;

public class FunDemo04 {

    /**
     * isAlive方法
     * @param args
     */
    public static void main(String[] args) {
    
        System.out.println("main  start ...");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " .... ");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println("线程的状态:"+t1.isAlive());
        t1.start();
        System.out.println("线程的状态:"+t1.isAlive());
        System.out.println("main  end ...");
    }

}

输出结果

mian   start	...
线程的状态:false
线程的状态:true
main	end	...
Thread-0	...

7.join

调用某线程的该方法,将当前线程和该线程合并,即等待该线程结束,在恢复当前线程的运行

package com.bobo.fundemo;

public class FunDemo05 {

/**
 * 线程的合并
 *     join方法
 * @param args
 */
public static void main(String[] args) {
    System.out.println("main  start ...");
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for(int i = 0 ; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + " 子线程执行了...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    });
    t1.start();
    try {
        t1.join(); // 线程的合并,和主线程合并  相当于我们直接调用了run方法
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("main end ...");
	}
}

输出结果:

main  start ...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
main end ...

8.yield

让出CPU,当前线程进入就绪状态

package com.bobo.fundemo;

public class FuneDemo06 extends Thread{

    public FuneDemo06(String threadName){
        super(threadName);
    }
    
    /**
     * yield方法  礼让
     *
     * @param args
     */
    public static void main(String[] args) {
        FuneDemo06 f1 = new FuneDemo06("A1");
        FuneDemo06 f2 = new FuneDemo06("A2");
        FuneDemo06 f3 = new FuneDemo06("A3");
    
        f1.start();
        f2.start();
        f3.start();
    }
    
    @Override
    public void run() {
        for(int i = 0 ; i < 100; i ++){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            if(i%10 == 0 && i != 0){
                System.out.println(Thread.currentThread().getName()+" 礼让:" + i);
                Thread.currentThread().yield(); // 让出CPU
            }else{
                System.out.println(this.getName() + ":" + i);
            }
        }
    }

}

9.wait和notify/notifyAll

阻塞和唤醒的方法,是Object中的方法,我们在数据同步的时候会介绍到

25.介绍下线程的生命周期

生命周期:对象从创建到销毁的全过程

线程的生命周期:线程对象(Thread)从开始到销毁的全过程

 

线程的状态:

  1. 创建Thread对象

  2. 就绪状态 执行start方法后线程进入可运行的状态

  3. 运行状态 CPU运行

  4. 阻塞状态 运行过程中被中断(等待阻塞,对象锁阻塞,其他阻塞)

  5. 终止状态 线程执行完成

26.为什么wait,notify和notifyAll这些方法不在thread类里面?

明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作所以把他们定义在Object类中因为锁属于对象。

27.为什么wait和notify方法要在同步块中调用?

1.只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。

2.如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。

3.还有一个原因是为了避免wait和notify之间产生竞态条件。

wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法和同步代码块中才能调用该对象的wait()方法。

在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。

调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:“特殊状态已经被设置''。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。

28.synchronize和ReentrantLock的区别

相似点:

这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.

区别:

这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句来完成。

  Synchronized进过编译,会在同步块的前后分别形成monitorentermonitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。

2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象

29.什么是线程安全

线程安全就是说多线程访问同一段代码,不会产生不确定的结果。

如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。这个问题有值得一提的地方,就是线程安全也是有几个级别的:

1)不可变   像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用 (2)绝对线程安全   不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet (3)相对线程安全   相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。 (4)线程非安全   这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类

30.Thread类中yieId方法的作用

yieId方法可以暂停当前正在执行的线程对象,让其它相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

31.常用的线程池有哪些

new SingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 new FixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 new CachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 new ScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的求。

32.简述一下你对线程池的理解

如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略合理利用线程池能够带来三个好处。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

public ThreadPoolExecutor(int corePoolSize,
                               int maximumPoolSize,
                               long keepAliveTime,
                               TimeUnit unit,
                               BlockingQueue<Runnable> workQueue,
                               RejectedExecutionHandler handler) 

参数含义:

corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
handler:超出线程范围和队列容量的任务的处理程序

线程池工作原理:

提交一个任务到线程池中,线程池的处理流程如下:

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

  2. 线程池判断工作队列是否已满,如果工作列没有满,则将提交新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

  3. 判断线程池里线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了则交给饱和策略来处理这个任务。

 

33.线程池的拒绝策略有哪些?

主要有4种拒绝策略:

  1. AbortPolicy:直接丢弃任务,抛出异常,这是默认策略

  2. CallerRunsPolicy:只用调用者所在的线程来处理任务

  3. DiscardOldestPolicy:丢弃等待队列中最旧的任务,并

  4. DiscardPolicy: 直接丢弃任务,也不抛出异常

34.线程安全需要保证几个基本特性?

原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。

可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为线程本地状态反映到主内存上,volatile就是负责保证可见性的。

有序性,是保证线程内串行语义,避免指令重排等。

35.说下线程间是如何通信的?

线程之间的通信有两种方式:共享内存和消息传递。

共享内存

在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。典型的共享内存通信方式,就是通过共享对象进行通信。

 

例如线程A与线程B之间如果要通信的话,那么就必须经历下面两个步骤:

  1. 线程A把本地内存A更新过得共享变量刷新到主内存中去。

  2. 线程B到主内存中去读取线程A之前更新过的共享变量。

消息传递

在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信

在Java中典型的消息传递方式,就是wait()和notify(),或者BlockingQueue

36.说说ThreadLocal的原理

ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离,相比于synchronized的做法是用空间来换时间。

ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存key value键值对的能力。

弱引用的目的是为了防止内存泄漏,如果是强引用那么ThreadLocal对象除非线程结束否则始终无法被回收,弱引用则会在下一次GC的时候被回收。

但是这样还是会存在内存泄漏的问题,假如key和ThreadLocal对象被回收之后,entry中就存在key为null,但是value有值的entry对象,但是永远没办法被访问到,同样除非线程结束运行。

但是只要ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出现这个问题的。

 

37.解释下:同步、异步、阻塞、非阻塞

同步和异步指的是:当前线程是否需要等待方法调用执行完毕。

阻塞和非阻塞指的是:当前接口数据还未准备就绪时,线程是否被阻塞挂起

同步&异步其实是处于框架这种高层次维度来看待的,而阻塞&非阻塞往往针对底层的系统调用方面来抉择,也就是说两者是从不同维度来考虑的。

这四个概念两两组合,会形成4个新的概念,如下:

同步阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,则客户端被服务端堵塞了,所以客户端会一值等待服务端的响应,此时客户端不能做其它任何事,服务端也不会接受其它客户端的请求。这种通信机制比较简单粗暴,但是效率不高。

同步非阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,这个时候虽然客户端会一直等待响应,但是服务端可以处理其他的请求,过一会回来处理原先的。这种方式很高效,一个服务端可以处理很多请求,不会在因为任务没有处理完而堵着,所以这是非阻塞的。

异步阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,但是客户端不会等待服务器响应,它可以做其他的任务,等服务器处理完毕后再把结果响应给客户端,客户端得到回调后再处理服务端的响应。这种方式可以避免客户端一直处于等待的状态,优化了用户体验,其实就是类似于网页里发起的ajax异步请求。

异步非阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,这个时候的任务虽然处理时间会很久,但是客户端可以做其他的任务,因为他是异步的,可以在回调函数里处理响应;同时服务端是非阻塞的,所以服务端可以去处理其他的任务,如此,这个模式就显得非常的高效了。

 

38.什么是BIO?

BIO同步并阻塞 ,服务器实现一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,没处理完之前此线程不能做其他操作(如果是单线程的情况下,我传输的文件很大呢?),当然可以通过线程池机制改善。

BIO方式 适用于连接数目比较小且固定的架构 ,这种方式对服务器资源要求比较高,并发局限于应用中JDK1.4以前的唯一选择,但程序直观简单易理解。

39.什么是NIO?

NIO同步非阻塞 ,服务器实现一个连接一个线程,即客户端发送的连接请求都会注册到多路复用器上,多复用器轮询到连接有I/O请求时才启动一个线程进行处理。

NIO方式 适用于连接数目多且连接比较短(轻操作)的架构 ,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4之后开始支持。

40.什么是AIO?

AIO异步非阻塞 ,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理,AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用操作系统参与并发操作,编程比较复杂,JDK1.7之后开始支持。

AIO属于NIO包中的类实现,其实 IO主要分为BIO和NIO ,AIO只是附加品,解决IO不能异步的实现在以前很少有Linux系统支持AIO,Windows的IOCP就是该AIO模型。但是现在的服务器一般都是支持AIO操作

41.介绍下IO流的分类

 

42.HTTP响应码有哪些

1、1xx(临时响应)

2、2xx(成功)

3、3xx(重定向):表示要完成请求需要进一步操作

4、4xx(错误):表示请求可能出错,妨碍了服务器的处理

5、5xx(服务器错误):表示服务器在尝试处理请求时发生内部错误

举例:

200:成功,Web服务器成功处理了客户端的请求。

301:永久重定向,当客户端请求一个网址的时候,Web服务器会将当前请求重定向到另一个网址,搜索引擎会抓取重定向后网页的内容并且将旧的网址替换为重定向后的网址。

302:临时重定向,搜索引擎会抓取重定向后网页的内容而保留旧的网址,因为搜索引擎认为重定向后的网址是暂时的。

400:客户端请求错误,多为参数不合法导致Web服务器验参失败。

404:未找到,Web服务器找不到资源。

500:Web服务器错误,服务器处理客户端请求的时候发生错误。

503:服务不可用,服务器停机。

504:网关超时

43.Forward和Redirect的区别?

  1. 浏览器URL地址:Forward是服务器内部的重定向,服务器内部请求某个servlet,然后获取响应的内容,浏览器的URL地址是不会变化的;Redirect是客户端请求服务器,然后服务器给客户端返回了一个302状态码和新的location,客户端重新发起HTTP请求,服务器给客户端响应location对应的URL地址,浏览器的URL地址发生了变化。

  2. 数据的共享:Forward是服务器内部的重定向,request在整个重定向过程中是不变的,request中的信息在servlet间是共享的。Redirect发起了两次HTTP请求分别使用不同的request。

  3. 请求的次数:Forward只有一次请求;Redirect有两次请求。

 

44.Get和Post请求的区别

用途:

  • get请求用来从服务器获取资源

  • post请求用来向服务器提交数据

表单的提交方式:

  • get请求直接将表单数据以name1=value1&name2=value2的形式拼接到URL上(http://www.baidu.com/action?name1=value1&name2=value2),多个参数参数值需要用&连接起来并且用?拼接到action后面;

  • post请求将表单数据放到请求头或者请求的消息体中。

传输数据的大小限制:

  • get请求传输的数据受到URL长度的限制,而URL长度是由浏览器决定的;

  • post请求传输数据的大小理论上来说是没有限制的。

参数的编码:

  • get请求的参数会在地址栏明文显示,使用URL编码的文本格式传递参数;

  • post请求使用二进制数据多重编码传递参数。

缓存处理:

  • get请求可以被浏览器缓存被收藏为标签;

  • post请求不会被缓存也不能被收藏为标签

45.介绍下OSI七层和TCP/IP四层的关系

为了更好地促进互联网的研究和发展,国际标准化组织ISO在1985 年指定了网络互联模型。OSI 参考模型(Open System Interconnect Reference Model),具有 7 层结构

 

应用层:各种应用程序协议,比如HTTP、HTTPS、FTP、SOCKS安全套接字协议、DNS域名系统、GDP网关发现协议等等。

表示层:加密解密、转换翻译、压缩解压缩,比如LPP轻量级表示协议。

会话层:不同机器上的用户建立和管理会话,比如SSL安全套接字层协议、TLS传输层安全协议、RPC远程过程调用协议等等。

传输层:接受上一层的数据,在必要的时候对数据进行分割,并将这些数据交给网络层,保证这些数据段有效到达对端,比如TCP传输控制协议、UDP数据报协议。

网络层:控制子网的运行:逻辑编址、分组传输、路由选择,比如IP、IPV6、SLIP等等。

数据链路层:物理寻址,同时将原始比特流转变为逻辑传输路线,比如XTP压缩传输协议、PPTP点对点隧道协议等等。

物理层:机械、电子、定时接口通信信道上的原始比特流传输,比如IEEE802.2等等。

而且在消息通信的过程中具体的执行流程为:

 

 

网络传输的数据其实会通过这七层协议来进行数据的封装和拆解

46.说说TCP和UDP的区别

1、TCP面向连接(如打电话要先拨号建立连接):UDP是无连接的,即发送数据之前不需要建立连接。 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。 3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。 4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP对系统资源要求较多,UDP对系统资源要求较少。

47. 说下HTTP和HTTPS的区别

端口不同:HTTP和HTTPS的连接方式不同没用的端口也不一样,HTTP是80,HTTPS用的是443 消耗资源:和HTTP相比,HTTPS通信会因为加解密的处理消耗更多的CPU和内存资源。 开销:HTTPS通信需要证书,这类证书通常需要向认证机构申请或者付费购买。

48.说下HTTP、TCP、Socket的关系是什么?

  • TCP/IP代表传输控制协议/网际协议,指的是一系列协议族。

  • HTTP本身就是一个协议,是从Web服务器传输超文本到本地浏览器的传送协议。

  • Socket是TCP/IP网络的API,其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面。对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

综上所述:

  • 需要IP协议来连接网络

  • TCP是一种允许我们安全传输数据的机制,使用TCP协议来传输数据的HTTP是Web服务器和客户端使用的特殊协议。

  • HTTP基于TCP协议,所以可以使用Socket去建立一个TCP连接。

49. 说下HTTP的长链接和短连接的区别

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

短连接   在HTTP/1.0中默认使用短链接,也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端访问的某个HTML或其他类型的Web资源,如JavaScript文件、图像文件、CSS文件等。当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话. 长连接   从HTTP/1.1起,默认使用长连接,用以保持连接特性。在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭。如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。

50.TCP原理

 

三次握手:

1.第一次握手:客户端将标志位syn重置为1,随机产生seq=a,并将数据包发送给服务端 2.第二次握手:服务端收到syn=1知道客户端请求连接,服务端将syn和ACK都重置为1,ack=a+1,随机产一个值seq=b,并将数据包发送给客户端,服务端进入syn_RCVD状态。 3.第三次握手:客户端收到确认后,检查ack是否为a+1,ACK是否为1,若正确将ACK重置为1,将ack改为b+1,然后将数据包发送给服务端服务端检查ack与ACK,若都正确,就建立连接,进入ESTABLISHEN.

四次挥手:

1.开始双方都处于连接状态 2.客户端进程发出FIN报文,并停止发送数据,在报文中FIN结束标志为1,seq为a连接状态下发送给服务器的最后一个字节的序号+1,报文发送结束后,客户端进入FIN-WIT1状态。 3.服务端收到报文,向客户端发送确认报文,ACK=1,seq为b服务端给客户端发送的最后字节的序号+1,ack=a+1,发送后客户端进入close-wait状态,不再发送数据,但服务端发送数据客户端一九可以收到(城为半关闭状态)。 4.客户端收到服务器的确认报文后,客户端进入fin-wait2状态进行等待服务器发送第三次的挥手报文。 5.服务端向fin报文FIN=1ACK=1,seq=c(服务器向客户端发送最后一个字节序号+1),ack=b+1,发送结束后服务器进入last-ack状态等待最后的确认。 6.客户端收到是释放报文后,向服务器发送确认报文进入time-wait状态,后进入close 7.服务端收到确认报文进入close状态。

51. Cookie和Session的区别

  cookie是由Web服务器保存在用户浏览器上的文件(key-value格式),可以包含用户相关的信息。客户端向服务器发起请求,就提取浏览器中的用户信息由http发送给服务器

  session是浏览器和服务器会话过程中,服务器会分配的一块储存空间给session。服务器默认为客户浏览器的cookie中设置sessionid,这个sessionid就和cookie对应,浏览器在向服务器请求过程中传输的cookie包含sessionid,服务器根据传输cookie中的sessionid获取出会话中存储的信息,然后确定会话的身份信息。

1、Cookie数据存放在客户端上,安全性较差,Session数据放在服务器上,安全性相对更高 2、单个cookie保存的数据不能超过4K,session无此限制 3、session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当 使用cookie。

52.Tomcat是什么?

Tomcat服务器Apache软件基金会项目中的一个核心项目,是一个免费的开放源代码的Web应用服 务器(Servlet容器),属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被 普遍使用,是开发和调试JSP程序的首选。

53.Tomcat有几种部署方式

  1. 利用Tomcat的自动部署:把web应用拷贝到webapps目录(生产环境不建议放在该目录中)。Tomcat在启动时会加载目录下的应用,并将编译后的结果放入work目录下。

  2. 使用Manager App控制台部署:在tomcat主页点击“Manager App”进入应用管理控制台,可以指定一个web应用的路径或war文件。

  3. 修改conf/server.xml文件部署:在server.xml文件中,增加Context节点可以部署应用。

  4. 增加自定义的Web部署文件:在conf/Catalina/localhost/路径下增加xyz.xml文件,内容是Context节点,可以部署应用。

54.什么是Servlet

  Servlet是JavaEE规范的一种,主要是为了扩展Java作为Web服务的功能,统一接口。由其他内部厂 商如tomcat,jetty内部实现web的功能。如一个http请求到来:容器将请求封装为servlet中的 HttpServletRequest对象,调用init(),service()等方法输出response,由容器包装为httpresponse 返回给客户端的过程。

55. 什么是Servlet规范?

  • 从Jar包上来说,Servlet规范就是两个Jar文件。servlet-api.jar和jsp-api.jar,Jsp也是一种Servlet。

  • 从package上来说,就是javax.servlet和javax.servlet.http两个包。

  • 从接口来说,就是规范了Servlet接口、Filter接口、Listener接口、ServletRequest接口、ServletResponse接口等。类图如下:

56.为什么我们将tomcat称为Web容器或者Servlet容器?

我们用一张图来表示他们之间的关系:

简单的理解:启动一个ServerSocket,监听8080端口。Servlet容器用来装我们开发的Servlet。

57.Servlet的生命周期

58. jsp和Servlet的区别

  • 本质都是servlet

  • servlet侧重于逻辑处理

  • jsp侧重于视图显示

59. 九大内置对象

  1. page页面对象

  2. config配置对象

  3. request请求对象

  4. response响应对象

  5. session会话对象

  6. application全局对象

  7. out输出对象

  8. pageContext页面上下文对象

  9. exception异常对象

60. JSP的四大作用域

page:

只在当前页面有效

request:

它在当前请求中有效

session:

它在当前会话中有效

application:

他在所有的应用程序中都有效

注意:当4个作用域对象都有相同的name属性时,默认按照最小的顺序查找

61. GenericServlet和HttpServlet有什么区别?

GenericServlet 为抽象类,定义了一个通用的、独立于底层协议的servlet,实现了Servlet 和 ServletConfig接口,ServletConfig接口定义了在Servlet初始化的过程中由Servlet容器传递给Servlet得配置信息对象。OK,这个类可能我们不是那么熟悉,但是他的子类相信大家都知道,也就是HttpServlet,HttpServlet 继承自抽象类GenericServlet 具有其所有的特性并拓展了一些其他的方法,如doGet、doPost等

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;