Java教程

Java面试题总结(持续更新)

本文主要是介绍Java面试题总结(持续更新),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JavaSE

Java基础

  1. 解释型语言和编译型语言的区别?Java是解释型语言还是编译型语言?

    编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。

    解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束!

    编译型语言,执行速度快、效率高;依靠编译器、跨平台性差些。

    解释型语言,执行速度慢、效率低;依靠解释器、跨平台性好。

    个人认为,java是解释型的语言,因为虽然java也需要编译,编译成.class文件,但是并不是机器可以识别的语言,而是字节码,最终还是需要 jvm的解释,才能在各个平台执行,这同时也是java跨平台的原因。所以可是说java即是编译型的,也是解释型,但是假如非要归类的话,从概念上的定义,恐怕java应该归到解释型的语言中。

  2. JIT是什么

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKmKwpFE-1640407809272)(D:\Ego\Java\面试\20191015222132356.png)]

    java编译为字节码文件后,如果jvm发现某段代码为“热点代码”,那么就会用JIT直接编译并进行优化。不是热点代码的话jvm就用解释器去解释。

    热点代码有两类:1.被多次调用的方法 2.被多次循环执行的方法体

    jvm识别热点代码需要进行热点探测,探测算法有两种:

    1. 基于采样

      jvm会周期性对各个线程栈顶进行检查,如果某些方法经常出现在栈顶,它就是热点方法。实现简单高效,但容易收到线程阻塞或其他外因干扰

    2. 基于计数器

      为每个方法或代码块建立计数器,超过一定阈值就认为是热点方法。结果严谨,但实现麻烦

    HotSpot采用第二种,并且有两类计数器:

    1. 方法调用计数器

      调用一次次数加一,超过一定时间(半衰周期)没有调用的话,次数会减半,称为“热度衰减”。

    2. 回边计数器

      统计循环体执行的次数。字节码中遇到控制流向后跳转的指令称为回边,遇到回边就加一

  3. 正则表达式和Java?

    正则表达式就是一种字符串的匹配模式,可以将一些复杂的规则用一个表达式来描述。

    Java的String类提供了支持正则表达式操作的方法:matches() replaceAll() replaceFirst() split()

    此外也可以用Pattern类表示正则表达式对象

  4. Java如何跳出多重嵌套循环?

    在最外层循环前加一个标记,比如a: 然后break a即可跳出,但是不建议这样用,会让代码可读性变差

    java也有关键字goto,但是没有用

  5. int和Integer的区别?

    Java是面向对象语言,所以为了能将基本数据类型当对象操作,Java为他们各自提供了包装类型,Integer就是int的包装类型,从Java5开始引入了自动装箱/拆箱,二者可以相互转换

  6. 如何输出一个某种编码的字符串

    String a = "abcde";
    //将a以UTF-8的编码方式获得字节数组,再以GBK的编码方式编码为b
    String b = new String(a.getBytes("UTF-8"), "GBK");
    System.out.println(b);
    
  7. 请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?

    Array声明的时候就要确定长度,而且长度不可变,只能存储同一数据类型,可以存储基本数据类型

    ArrayList是一个集合,长度可变,可以存放不同数据类型,不可以存放基本数据类型(可以存他们的包装类)。

    当能确定数据类型和个数时可以用Array

  8. 什么是自动拆装箱?

    Java八种基本数据类型都有各自对应的包装类,基本类型和对应的包装类在很多场景下可以自动转换,不需要手动去转换。

    实现原理:

    • 自动装箱:基本类型–>包装类 。调用 包装类.valueOf() , 比如Integer.valueOf(1)
    • 自动拆箱:包装类–>基本类型。调用xxxValue,比如有一个Integer对象a,则为a.intValue()

    哪些地方自动拆装箱:

    1. 向集合里存基本数据类型,会自动装箱
    2. 包装类型和基本类型比较大小时,包装类型会自动拆箱
    3. 包装类型四则运算,会自动拆箱
    4. 三目运算会自动拆箱,所以如果值为null,会报空指针异常
    5. 函数参数与返回值
  9. 为什么会出现4.0-3.6=0.40000001?

    计算机计算十进制时要先转换成二进制,而二进制无法精确表示十进制小数,所以会出现误差

    可以用BigDecimal来解决这个问题。

  10. 十进制的数在内存中是怎么存的?

补码的形式

  1. Java8新特性?

    1. 对HashMap的数据结构进行优化

      HashMap1.8以前是数组+链表,1.8以后是数组+链表/红黑树

    2. Lambda表达式,本质是一段匿名内部类

    3. 函数时接口,给Lambda提供更好的支持

    4. Stream API:创建Stream,中间操作,终止操作

    5. Optional类:用来解决空指针异常

    6. 接口中可以定义m默认实现方法和静态方法

    7. 日期API

  2. Object若不重写hashCode()的话,hashCode()如何计算出来的?

    Object的hashCode是本地方法,用c/c++实现的,直接返回该对象的内存地址

  3. 请你解释为什么重写equals还要重写hashcode?

    1. 提升效率

      向HashMap中put时,首先会计算对象的哈希码,然后看看对应的位置上有没有元素,如果没有的话就可以直接插入了,就不需要从头到尾和每个元素都进行equals判断了。

    2. 还有为了保证HashMap和HashSet中的去重性。因为equals相同的对象,hashcode必须相同,如果只重写equals的话,两个属性相同的对象按照规则hash值也一样,但是没有hashcode的话会调用Object默认的的hashcode,这样值就不相同了。比如两个相同的属性的对象,hashcode值不同,HashMap认为是两个不同的对象,都会存进去,但是我们想要的结果是只存一个,这就违反了HashMap的唯一性了。

关键字

  1. 请你谈谈关于Synchronized和lock

    synchronizedLock
    Java关键字一个接口
    线程执行完或发生异常会释放锁,不会出现死锁需要在finally中手动释放锁,不然容易造成死锁
    不可以中断等待锁的线程可以中断等待锁的线程
    不可以判断锁的状态可以判断锁的状态
    大量线程竞争时性能低大量线程竞争时性能高
  2. 请你介绍一下volatile?

    volatile可以用来保证可见性和有序性。

    先说下内存模型:计算机执行程序时,每条指令都是在CPU上执行,那就涉及到了数据的读写。CPU的运行速度很快,而向主存读写数据的速度很慢,所以就在两者之间加了高速缓存。先把主存的数据复制一份到高速缓存中,然后CPU就可以从高速缓存中进行读写,最后高速缓存再将值刷新到主存中。那么多线程就会出现问题,比如主存中有一个数为0,现在有两个线程想对它加一,那么结果应该为2。但是可能第一个线程计算为1之后还没来得及写入主存,第二个线程就进行运算,也就是读取的也是0,那么最后结果为1。所以就提供了两种办法:

    1. 在总线上加LOCK#锁,那么对于某个变量只能有一个线程访问,但是效率低下。
    2. 缓存一致性协议。当一个CPU写数据时,如果其他CPU中也有这个变量的副本,会让其他CPU重新读取。

    这就是可见性问题。

    有序性问题就是说,JVM在执行时会对程序执行顺序进行优化,比如int a = 1, int b = 2。在实际执行时可能会将他们的顺序交换,但不会造成影响,因为这两个变量没有依赖关系,这就是指令重排序。单线程下是不会造成问题,但是多线程就可能出现问题。比如第一个线程先定义了某个变量,然后定义了一个标志位为true,第二个线程中假设标志位为true的话就使用第一个变量,那么重排序后,第一个线程可能设置标志位为true提前了,第二个线程认为可以使用了,但其实变量还没定义,那么就会出错。Java内存模型具备一定的有序性,即happens-before原则

    在Java中,Java模型为了获得更好的性能,允许处理器使用高速缓存,也允许编译器进行指令重排序,所以也会出现这两个问题。

    那么volatile修饰的变量有两个作用:

    1. 某个线程修改了这个变量后,其他线程立即可见。
    2. 禁止指令重排序。

    但是volatile不可以保证原子性,比如要对a = 0 自增,开启两个线程,每个线程循环100次a++,那么结果可能是小于200的。因为a++不是原子操作,它分为三个步骤,先读取a,再加一,再写会,那么第一个线程第一次加的时候,可能刚读取完,然后被阻塞了,第二个线程再读的时候还是原来的值,加完之后写了回去,这时第一个线程阻塞完毕继续再原来的基础上加1,然后写回,那么两次操作其实只加了1。

    volatile底层原理是,在生成汇编代码时会多出一个lock前缀指令,这个指令相当于一个内存屏障,它提供了3个功能:

    1. 保证重排序时,不会把它后面的指令排序到它前面,也不会把前面的指令排到后面
    2. 强制对缓存的修改操作立即写入主存
    3. 如果是写操作,其他CPU对应的缓存行无效

面向对象

  1. 重载和重写的区别?

    两个都是实现多态的方式。重载是编译时多态,重写是运行时多态。重载发生在同一个类中,要求方法名一样,参数列表不一样,对返回值没有要求;重写发生在子类与父类中,子类重写父类的方法要求方法名,参数列表一样,返回值一样或者为父类返回值的字类,访问修饰符不能小于父类,不能抛出新的异常或更宽泛的异常

  2. 面向对象的六原则一法则?

    1. 单一职责原则:一个类只做它该干的事情,也就是实现高内聚
    2. 开闭原则:一个软件实体应该对扩展开放,对修改闭合。这样增加新功能时只用派生一些新类,而不用修改原来的代码
    3. 依赖倒转原则:尽可能使用抽象类型而不是具体类型
    4. 里氏替换原则:任何时候都可以用子类替换掉父类。如果这样做出现了问题那继承一定是错误的
    5. 接口隔离原则:接口要小而专,不能大而全
    6. 合成聚合服用原则:优先使用合成和聚合关系复用代码
    7. 迪米特法则:一个对象尽可能对其他对象了解的少,也就是做到低耦合
  3. 在try块中可以抛出异常吗?

    可以,比如IO流读取File的时候,外层try catch包含创建流的代码,内层try catch来操作流,这样如果流创建失败直接抛异常,就不用关闭流了。

  4. 抽象类和接口的区别?

    • 语法上:

      1. 抽象类可以有构造方法,接口不能有
      2. 抽象类可以包含非抽象的普通方法,接口不可以
      3. 抽象类可以有成员变量,接口不可以
      4. 一个类可以实现多个接口,但只能继承一个抽象类
    • 应用上:

      接口是横向的,抽象类是纵向的,接口约定了一个共同的行为,而抽象类是把一些子类的共性抽取出来,可以帮他们完成一部分方法的实现。所以需要横向扩展就用接口,纵向扩展就用抽象类。

      举个例子,比如某个项目的所有Servlet都需要进行权限判断,记录日志、异常等操作,就可以定义一个抽象类,定义一个抽象方法,里面写具体的业务逻辑,然后定义一个非抽象的方法,去完成权限判断,记录日志的操作,然后去调用这个抽象方法,那么子类去继承他的时候只需要重写业务逻辑的抽象方法就可以了,别的操作就自动帮他完成了。

  5. 请说明一下final, finally, finalize的区别?

    1. final:

      修饰属性表示这个属性不可变,是一个常量。

      修饰方法表示这个方法不可以被重写。

      修饰类表示这个类不可以被继承。

    2. finally:

      用于异常处理,无论是否抛出异常,finally中的代码一定会执行,所以一般用于资源的关闭。

    3. finalize:

      是Object类中的一个方法,当垃圾收集器回收时,被回收的对象会调用此方法,以供其他资源回收

  6. 请说明面向对象的特征有哪些方面?

    1. 封装

      就是要做到高内聚低耦合,把一个对象的属性和行为都封装到一个类中,把成员变量定义为私有。

    2. 继承

      就是把父类的属性和方法继承过来,然后添加一些自己需要的新的东西,做到了可重用性和扩展性

    3. 多态

      就是父类引用指向子类对象。分为编译时多态和运行时多态,重载实现了编译时多态,重写实现了运行时多态

  7. 请说明Comparable和Comparator接口的作用以及它们的区别?

    两个接口都是来定义排序规则的。

    • Comparable:

      相当于内部比较器,比如对集合进行排序一般会用到Collections.sort()方法,但是集合中的这个类必须实现Comparable接口,并重写他的compareTo方法,才可以直接用Collections进行排序

    • Comparator:

      相当于外部比较器,如果一个类我们没办法对他进行扩展,也就是无法继承Comparable,那就可以使用Comparator,在用Collections.sort()方法时,里面传入集合以及一个匿名内部类,这个内部类去实现Comparator接口,并重写compare()方法。比如String类型中,默认的比较规则是按照字典序排序,如果我们想忽略大小写进行排序的话,就可以使用Comparator

  8. 请你讲讲什么是泛型?

    泛型就是参数化类型,就是将操作的数据类型指定为一个参数,可以在类、接口、方法中使用。

    泛型提高了代码的重用率,编译时可以检查类型安全,消除了强制转换,减少了出错的机会。

  9. 请解释一下extends 和super 泛型限定符?

    extends用来定义上界,super定义下界。

    • 上界的list只能get,不能add。

      因为extends表示它和它的子类,但是具体add哪一个子类是不确定的,所以干脆就不让它add

      但是get的话,无论是哪一个子类,都可以向上转型成上界

    • 下界的list只能add,不能get。

      super表示它和它的父类,父类那么多不知道add哪个,所以只能add他和他的子类,虽然不知道add哪个,但是都可以向上转型成下界。

      而get的话,那么多父类是不能向下转型的,除非用Object来接收。

    所以如果想取数据就使用extends,想存数据就用super

  10. 请说明”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

static表明让成员变量或成员方法所属于一个类,而不是对象,被static修饰的成员变量和方法可以直接通过类名.的方式被访问,独立于对象。

static还可以当作静态代码块,在类第一次被加载的时候执行,只会被执行一次,一般用作初始化,提高性能。

Java不可以覆盖private或static修饰的方法。private只用本类能访问到,子类是访问不到的,更别说覆盖了。

而static修饰的静态方法,跟任何实例无关,在编译时就绑定了,但覆盖是在运行时动态绑定,所以概念上不适用。

  1. 请列举你所知道的Object类的方法并简要说明

    1. hashCode():本地方法,用来获取对象的哈希值,用来确定对象的存储位置
    2. equals():用于确定两个对象是否相同
    3. clone():用来创建并返回当前对象的一个拷贝
    4. toString():返回对象的字符串表示形式
    5. getClass():本地方法,返回运行时的Class对象,被final修饰,不能被重写
    6. notify():本地方法,final修饰不能被重写,唤醒一个在此对象监视器上等待的线程
    7. notifyAll():同notify(),只是唤醒的是所有线程
    8. 三个wait():本地方法,final修饰不能被重写,用来暂停线程的执行。没参数的会一直等待下去,一个参数的传入等待时间,两个参数的传入等待时间和额外时间
    9. finalize():垃圾收集器回收时对象调用此方法进行资源的回收
  2. 类和对象的区别?

    类是一个抽象的概念,是对具有共同特征的实体的集合。

    对象是类的实例,是真实的个体,和真实世界一一对应的。

  3. String为什么不可变?为什么要这样设计?

    因为String的本质是char数组,而char数组是被final修饰的,所以不可以变。

    为什么要这样设计:

    1. 字符串常量池的需要

      Java堆内存中有一块区域是字符串常量池。当创建一个String对象时,如果常量池已经有这个字符串了,那就不会再创建,而是直接将引用指向它。如果有好几个引用都指向了一个常量池中的对象,这个时候如果有一个引用想要改变它的话,那所有的引用指向的就都被改变了,显然不合理。

    2. 效率

      String中有hash来保存它的哈希码,String设置为不可变的话,每次使用的时候就不需要重复计算哈希码,提高了效率

    3. 安全

      Java很多类都使用了String,比如网络连接,反射等等,如果String是可变的话h会引起安全隐患

集合

  1. List、Map、Set三个接口,存取元素时,各有什么特点?

    List和Set都是单列集合,Map是双列集合。

    List有先后顺序,Set和Map是无序的。

    • List:

      存的时候调用add()方法。

      取的时候用get()或者用Iterator接口遍历

    • Set:

      不能有重复的元素。存的时候调用add()方法,返回值为boolean,如果为true说明集合中没有这个元素,成功存进去,如果为false说明已经存在了。

      取的时候用Iterator接口遍历

    • Map:

      以键值对形式存储,不能有重复的键。存的时候调用put()方法

      取的时候可以调用get(),根据key来找相应的value;也可以通过keySet()获取所有key的集合;也可以通过values()获取所有value的集合;也可以通过entrySet()获取所有键值对的集合

    Set和Map都有哈希存储的版本和排序树存储的版本,哈希存储版本存取非常高效,而排序树版本可以自定义排序规则

  2. ArrayList、LinkedList、Vector的区别和实现原理

    • 存储结构

      ArrayList和Vector基于数组实现,LinkedList基于双向链表实现。

    • 线程安全

      ArrayList和LinkedList不是线程安全的,可以用Collections中的静态方法synchronizedList()把他们变成线程安全的。Vector是线程安全的,大部分方法都包含synchronized,所以效率比较低,已经被遗弃了。

    • 扩容机制

      ArrayList如果元素个数超过数组长度,会产生一个新的数组,容量是原来的1.5倍,然后将原来的数据复制过来,再加上新的数据。

    • 效率

      ArrayList和Vector查询效率是O(1),平均增删的效率是O(n)

      LinkedList平均查询效率是O(n),增删效率是O(1)

  3. 请判断List、Set、Map是否继承自Collection接口?

    List和Set继承自Collection,Map不是

  4. 请讲讲你所知道的常用集合类以及主要方法

    List、Set、Map

    List:具体实现有ArrayList和LinkedList,常用方法有get(),add(),remove(),contains(),size()

    Set:具体实现有HashSet和TreeSet,常用方法有add(),remove(),contains(), size()

    Map:具体实现有HashMap和TreeMap,常用方法有get(),put(),remove(),containsKey(),containsValue(),keySet(),values(),entrySet()

  5. Collection和Collections的区别?

    Collection是集合类的父接口,实现有List和Set

    Collections是集合类的一个帮助类,提供了一系列静态方法来操作集合类,主要方法有:

    1. sort:排序方法,默认是升序,也可以实现Comparator接口来自定义排序规则
    2. reverse:翻转集合中元素的顺序
    3. shuffle:对集合随机排序
    4. copy:将第二个参数集合中的元素复制到第一个参数集合中
    5. synchronizedList/synchronizedSet/synchronizedMap:将集合变为线程安全
  6. 请说说快速失败(fail-fast)和安全失败(fail-safe)的区别?

    快速失败:当用迭代器遍历一个集合时,如果这个集合被修改,那么就会抛出异常,所以不能在多线程下并发修改。原理:迭代时会用一个modeCount变量,如果集合遍历时被修改,那么modeCount的值就会改变,迭代器每次调用hasNext和next前都会判断modeCount和expectedmodeCount是否一样,如果不一样就会抛出异常。

    安全失败:迭代器遍历集合时,集合被修改也不会抛出异常。原理:迭代前会对原有集合进行一次拷贝,迭代器对拷贝的集合进行迭代,所以就算原有集合被修改,拷贝的集合也没有被修改,也就不会抛出异常

  7. 请你说说Iterator和ListIterator的区别

    两者都是迭代器。

    Iterator:可以应用于List和Set,只能向后遍历,而且只能获取和删除。

    ListIterator:

    只能应用于List,是对Iterator的一个增强。

    不仅可以向后遍历,也可以向前遍历,即多了hasPrevious()和previous()。

    可以获取当前索引的位置,即nextIndex()和previousIndex()。

    可以对当前对象进行修改,即set()

    可以插入对象,即add()

  8. 请你说明一下ConcurrentHashMap的原理?

    1.7:使用了分段锁Segment,Segment就类似于HashMap,也就是相当于一个二级哈希表。在put时,先根据key找到对应的Segment,然后在对应的Segment里面尝试获取锁,获取到锁之后就进行插入,和HashMap类似,最后再释放锁。在get时就很简单,因为它的value被volatile修饰了,所以保证了可见性,这样就不需要加锁了,效率就得到了提高。

    1.8:数据结构和1.8的HashMap类似,不使用Segment,而是采用了CAS和synchronized来实现并发安全的。put时,先尝试用CAS写入,如果失败就通过自旋保证成功。如果hashcode==-1就需要进行扩容。如果都不满足的话,就用synchronized进行写入。最后判断是否要转成红黑树。

  9. 请说明ArrayList是否会越界?

    会发生。因为ArrayList是线程不安全的,所以多线程情况下可能会发生越界。

  10. HashMap的容量为什么是2的n次幂?

为了散列更均匀,减小哈希碰撞。因为在计算key对应的下标时,计算方法是(n-1)&hash值,这其实就相当于取模的位运算形式,如果容量是2的n次幂的话,减去1之后就是低位全是1的形式,这样和hash进行与运算时会大大减小hash冲突。

  1. 如果一直在list的尾部添加元素,用哪种方式的效率高?

    在尾部添加元素的时间复杂度都是O(1),但是ArrayList需要扩容,而LinkedList需要new结点,所以在千万级别一下的时候,扩容的优势还不明显,LinkedList效率更高,但在千万级别以上ArrayList效率更高

  2. 请你解释一下hashMap具体如何实现的?

    jdk1.7以前是通过数组+链表实现的,jdk1.8改为了数组+链表/红黑树。使用时,先初始化HashMap,可以指定容量大小n,它会自动调用tabSizefor()函数来得到第一个大于等于n且为2的整数次幂的数,如果不指定容量的话,就默认为16。然后插入时,会先根据key计算得到hash值,然后通过hash值得到索引。拿到索引后先判断那个位置上有没有结点,如果没有的话直接插入,如果有的话,判断和头节点的key是否相等,如果相等的话直接覆盖,如果不相等的话,如果头节点是红黑树结点,就按照红黑树结点的查找方式遍历,如果是链表结点的话,按链表结点方式遍历,如果在遍历中找到key相同的结点就覆盖,如果没有的话,就插入到尾部,链表的话插入完判断结点是否超过8个而且数组长度超过64,如果超过的话就转为红黑树。最后判断是否需要扩容。

  3. HashMap扩容机制

    首先判断老表的容量是否超过上限,如果超过上限的话,将扩容阈值修改为Integer最大值,如果没超过的话,将容量和阈值都扩大为2倍,并指向新数组,然后遍历老数组,如果当前索引只有一个结点,则重新计算索引位置然后放到新数组;如果当前索引不止一个结点,则计算e.hash&oldCap,如果为0,则直接放到原索引位置,如果为1,则放到原索引+oldCap的位置。因为计算索引时,用的是(n-1)&hash,那么扩容为2倍之后,n-1和原来相比,就是在高位多出来一个1,低位还是一样的,所以只用判断这一位即可

多线程

  1. 如何实现线程安全?

    线程安全主要体现在原子性、可见性、有序性

    实现原子性:使用原子类、synchronized、lock

    实现可见性:volatile、synchronized、lock

    实现有序性:volatile、happens-before原则

  2. 线程的基本状态以及状态之间的关系?

    基本状态:新建、就绪、运行、阻塞、死亡

    1. 当线程被new出来后处于新建状态
    2. 线程调用start处于就绪状态
    3. 就绪的线程得到CPU的执行权后就进入运行状态
    4. 当运行态的线程因为某些原因放弃CPU,比如调用wait(),sleep(),就进入阻塞态。
    5. 休眠结束或被唤醒之后重新进入就绪态
    6. 执行完run方法或遇到了未捕获的异常就会进入死亡态
  3. 什么是守护线程?

    线程分为用户线程和守护线程,守护线程就是在后台提供一种通用服务的线程,比如垃圾回收器。当所有用户线程结束后,守护线程就会结束,因为没有存在的意义了。可以用过thread.setDaemon(true)来设置一个线程为守护线程,必须在start之前设置。守护线程中产生的新线程也是守护线程。守护线程中不可以有读写操作和计算逻辑,因为守护线程随时都有可能停止。

  4. 线程池的状态和状态之间的关系?

    5种状态: running、shutdown、stop、tidying、terminated

    1. 线程池被创建后处于running,可以接收任务和处理任务
    2. running时调用shutdown()方法进入shutdown,不接受新任务但可以处理已排队的任务
    3. 调用shutdownNow()方法进入stop,不接受新任务也不能处理已排队的任务,并且中断正在处理的任务
    4. shutdown下,任务队列为空且没有执行中的任务,会转为tidying;stop中执行任务为空也会转为tidying
    5. tidying执行完terminated()会转为terminated
  5. 如何停止线程池?

    1. shutdown()
    2. shutdownNow()
    3. shutdown() + awaitTermination()
  6. Java锁?

    并发编程中,为了避免共享数据产生数据不一致的问题,使用锁来锁定代码块、方法。

    锁分为:悲观锁、乐观锁、公平锁、非公平锁、独占锁、共享锁、自旋锁、可重入锁、偏向锁/轻量级锁/重量级锁

  7. 什么是死锁?原因和必要条件?怎么解决?

    死锁就是多个进程因为争夺资源而陷入的一种僵局,如果无外力作用就会一直保持下去。

    原因:竞争资源和推进顺序非法

    必要条件:

    1. 互斥条件:也就是竞争共享资源
    2. 请求和保持条件:进程因请求资源而阻塞时,不会释放已有的资源
    3. 不剥夺条件:进程已有的资源使用完之前不可以被剥夺,只能使用完自己释放
    4. 环路等待条件:死锁发生时,必然有一个进程资源环型链

    怎么解决:

    1. 预防死锁:

      破坏4个必要条件之一即可:

      1. 第一个条件不能破坏,因为加锁本来就是为了保证互斥
      2. 请求和保持:一次性分配资源
      3. 不可剥夺:如果获得一部分资源,别的资源获取不到时,释放自己已有的资源
      4. 环路等待:按顺序申请资源
    2. 避免死锁:银行家算法

    3. 检测死锁

    4. 解除死锁

  8. 什么是活锁和饥饿?

    活锁跟死锁相反,死锁是因为争夺资源而陷入僵局,活锁是都可以获取到资源,但是都相互谦让陷入的一种僵局,活锁有可能自行解开。比如让线程休眠一段时间,让别的线程先获取到资源。

    饥饿是一个线程一直获取不到资源,资源一直被别的线程获取,导致他可能永远等待。可能的原因是这个线程的优先级低,所以一直被高优先级的线程争夺到资源。

  9. 无锁技术

    1. 原子类
    2. ThreadLocal
    3. copy-on-write
    4. concurrent开头的并发工具类
  10. 什么是happens-before?

    JVM会对代码进行优化,进行指令重排序,happens-before就保证了重排序后依然可以正确执行代码。他的原则是如果一个操作happens-before另一个操作,那么第一个操作的结果对第二天操作可见。

    有6条规则:

    1. 先写的代码先执行
    2. 释放锁先于获取锁
    3. volatile修饰的变量,写操作先于读操作
    4. 线程的start()先于线程的其他操作
    5. 传递规则
    6. 被调用join()的线程的操作先于join()的返回
  11. sleep()和wait()的区别?

    sleep是Thread类的静态方法,wait是Object类的成员方法

    sleep可以在任何地方使用,wait只能在同步代码块或同步方法中使用

    sleep释放CPU资源,但不释放锁,休眠时间到后继续执行,wait释放锁,进入等待队列,被唤醒后才有机会获取锁

  12. notify和notifyAll的区别?

    一个线程中调用了一个对象的wait方法,该线程就会释放该对象的锁,进入该对象的等待池,等待池中的线程不会去竞争该对象的资源。一个对象还有一个锁池,只有在锁池中的线程才会去竞争该对象的锁。notify就是随机唤醒等待池中的一个线程进入锁池,notifyAll将等待池中所有的线程唤醒进入锁池

  13. 线程池中submit()和execute()方法有什么区别?

    submit参数为runnable或callable,execute参数为runnable

    submit有返回值,execute没有返回值

  14. ThreadLocal有什么作用?有哪些使用场景?

    ThreadLocal是本地线程存储,为每个线程创建一个该变量的副本,可以做到数据间的隔离。

    JDBC连接connection就使用到了,为每个线程创建一个自己的连接,这样就保证了他们是在各自的连接上进行数据库操作,不会出现A线程关了连接,而B线程还在用的情况

  15. 如何保证多个线程同时启动?

    使用countDownLatch。run方法中调用countDownLatch的await(),就会将线程阻塞在此处。然后调用start。

    在需要同时启动的地方调用countDownLatch的countDown()

  16. 说说对于sychronized同步锁的理解

    每个java对象都有一个内置锁,当线程运行到非静态的synchronized方法上时,就会自动获取该实例对象的锁,此时别的线程无法获得它的锁,当方法执行完毕后,会释放锁。

网络编程

  1. BIO、NIO、AIO有什么区别?

    BIO:同步阻塞,线程发起IO操作,不管内核是否准备好IO操作,线程都会一直阻塞直到完成。适用于连接数少的架构。

    NIO:同步非阻塞,发送的请求会注册到多路复用器上,多路复用器轮询到连接有IO请求时才会开启一个线程。适用于连接数多且连接比较短的架构。

    AIO:异步非阻塞,线程发起IO请求立即返回,内核做完IO操作后才会通知线程。适用于连接数多且连接比较长的架构。

  2. 如何读取文件a.txt中第10个字节?

    FileInputStream fis = new FileInputStream("a.txt");
    fis.skip(9);
    int b = fis.read();
    
  3. 节点流和处理流区别?

    节点流:可以从某个节点读数据或写数据的流

    处理流:对已有的流进行封装,提供更丰富的处理,构造方法必须是其他流的对象,比如BufferedReader

  4. 缓冲流的优缺点?

    不带缓存的流读一个字节或字符就写一个字节或字符,缓冲流读取到字节或字符先放入缓冲区,当缓冲区满了再一次性写出去。

    优点:减少了写的次数,提高效率

    缺点:接受端无法即时接收到数据

Spring

  1. spring的好处

    1. 方便解耦和开发,将对象的创建和依赖关系交给spring来处理
    2. 支持AOP编程,可以方便的实现权限拦截和监控等功能
    3. 声明式事务,通过配置就可以完成对事务的支持
    4. 方便集成别的框架
  2. 什么是IOC?

    IOC就是控制反转,是一种设计思想,把对象的创建交给spring来处理,可以解耦合,提高程序的复用性

  3. 什么是AOP?

    AOP就是面向切面编程,可以将一些与业务无关,但是被业务共同调用的逻辑封装起来,增加代码的复用性,降低模块间的耦合

  4. spring注入方式

    接口注入,setter注入,构造器注入

  5. spring的bean是线程安全吗?

    spring不能保证bean是线程安全的,因为默认bean是单例的,这样就可能存在竞争,就会造成线程不安全。

    可以用ThreadLocal或锁来解决这个问题。

  6. spring自动装配bean有哪些?

    XML方式:

    1. no:不进行自动装配
    2. byName:根据名字自动装配
    3. byType:根据类型自动装配
    4. constructor:根据构造器自动装配

    注解方式:

    1. @Autowired:通过类型自动装配,想要通过名字装配可以再加一个@Qualify注解来指定名称
    2. @Resource:先通过名称装配,找不到就通过类型装配
  7. Bean的生命周期

    分为4个阶段:实例化,属性赋值,初始化,销毁。

    实例化的时候,可以通过InstantiationAwareBeanPostProcessor接口来进行扩展,可以在实例化之前调用它的方法来替换原本的bean作为代理,这也是AOP能实现的关键点。可以在实例化之后,属性赋值之前调用它的方法来阻断属性填充。

    初始化阶段,可以调用BeanPostProcessor接口,一系列Aware接口,InitializingBean接口,来进行扩展。BeanPostProcessor接口作用在初始化的前后,Aware接口可以拿到Spring的一些资源,比如BeanName,BeanFactory,ApplicationContext,InitializingBean接口可以自定义一些初始化操作。初始化时调用init-method指定的方法。

    销毁阶段,可以通过DisposableBean接口来进行扩展,最后调用destroy-method指定的方法进行销毁

  8. BeanFactory 和 FactoryBean 的区别

    BeanFactory 是一个工厂类,用来管理Bean的,最核心的功能就是加载Bean,也就是getBean()方法。

    FactoryBean 是一个特殊的Bean,实现该接口的类可以实现它的getObject方法来自定义创建Bean实例

  9. BeanFactory 和 ApplicationContext 的区别

    BeanFactory 是一个基础的IOC容器,提供了完整的IOC功能。

    ApplicationContext是BeanFactory的一个子接口,是一个高级的IOC容器,提供了更多的功能。

    二者加载Bean的时机也不同。BeanFactory 是延迟加载,调用getBean()时才加载,而ApplicationContext在启动后就预加载所有单实例的Bean,到时候可以直接拿来使用

  10. Spring 的 AOP 有哪几种创建代理的方式?

有JDK动态代理和Cglib代理。

JDK动态代理只能代理实现了接口的类,因为通过JDK动态代理生成的类已经实现了Proxy类,所以不能继承别的类了。

而Cglib代理没有这个限制,但是他不可以代理被final修饰的类或方法,因为他的本质是通过继承实现的

  1. Spring是如何解决的循环依赖?

    Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。

  2. 为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

    如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

  3. Spring 的事务传播行为有哪些?

    1. REQUIRED:Spring 默认的事务传播级别,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。
    2. REQUIRES_NEW:每次都会新建一个事务,如果上下文中有事务,则将上下文的事务挂起,当新建事务执行完成以后,上下文事务再恢复执行。
    3. SUPPORTS:如果上下文存在事务,则加入到事务执行,如果没有事务,则使用非事务的方式执行。
    4. MANDATORY:上下文中必须要存在事务,否则就会抛出异常。
    5. NOT_SUPPORTED :如果上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
    6. NEVER:上下文中不能存在事务,否则就会抛出异常。
    7. NESTED:嵌套事务。如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

SpringMVC

  1. 什么是SpringMVC ?简单介绍下你对springMVC的理解?

    SpringMVC 是Spring框架的一部分,把model,view,controller层分离,把复杂的web应用分成逻辑清晰的几部分,可以做到解耦,简化开发,方便开发人员之间的配合

  2. SpringMVC的流程?

    1. 用户发送请求给DispatcherServlet
    2. DispatcherServlet调用HandlerMapping,请求获取Handler
    3. HandlerMapping根据url找到对应handler,返回给DispatcherServlet
    4. DispatcherServlet调用HandlerAdapter,请求执行Handler
    5. HandlerAdapter执行Handler,Handler执行完将ModelAndView返回给HandlerAdapter
    6. HandlerAdapter将ModelAndView返回给DispatcherServlet
    7. DispatcherServlet将ModelAndView传给ViewResolver进行视图解析
    8. ViewResolver解析完将View返回给DispatcherServlet
    9. DispatcherServlet对View渲染,返回给用户
  3. 如何解决POST请求中文乱码问题,GET的又如何处理呢?

    POST:在web.xml里配置CharacterEncodingFilter 过滤器

    GET:两种方法

    1. 可以将tomcat的编码修改,与工程保持一致
    2. 可以对参数进行重新编码
  4. @RequestMapping的作用是什么?

    用来标识HTTP请求地址与Controller之间的映射关系。

    可以加在类上,也可以加在方法上,完整的路径是类上的@RequestMapping的value加上方法上的RequestMapping的value

  5. SpringMvc的控制器是不是单例模式?如果是,有什么问题?怎么解决?

    是,在多线程访问时会出现线程不安全的问题。

    解决方法:控制器里对可变状态量使用ThreadLocal,为每个线程生成一个副本,互不影响

SpringBoot

  1. Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

    启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

    @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

    @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

    @ComponentScan:Spring组件扫描。

  2. SpringBoot自动装配原理

    Springboot通过启动类的@SpringBootApplication注解来实现自动装配。该注解是一个组合注解。包含三个注解

    1. @SpringBootConfiguration:用来进行spring的配置,该注解有@Configuration注解,@Configuration注解有@Component,所以说明他也是spring的一个组件
    2. @EnableAutoConfiguration:用来开启自动配置的注解。该注解主要由@AutoConfigurationPackage和@Import注解组成。@AutoConfigurationPackage注解也使用@Import注解,里面传入了Registrar.class,这个类里面有一个方法可以获得扫描包的路径。@Import注解中传入了一个组件选择器,里面有一个方法可以将需要导入的组件的全类名返回,这些组件就会被添加到容器中
    3. @ComponentScan:用类扫描spring组件的注解
这篇关于Java面试题总结(持续更新)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!