上一篇文章我发布了Java后端的90道高频面试题。大家如果看过这90道题,会发现一般常规的面试问题可以说都覆盖到了,看完应该还有一个感觉,每一道题都不是一二句话能简单讲完的,如果能把这些面试题以及关联的知识都掌握,相信对Java 的掌握程度也会更深刻一些。

还有读者说等着我这个去面试,今天学习群里一个读者说已经拿到6个offer了,希望我的读者都能offer爆炸。。

最近真是太累了,更新的慢一些,也是忙了大半年,公司双12、五福、三月开门红几场大促下来,想休息一下,看着朋友圈一帮已经找好工作,出去自驾的,搞得我也想出去玩了呀,不过大家放心,一定会在出去玩之前把题解都写完!

20道Java高频面试题-题解_java

这里是更新的20道题题解:

  1. 自我介绍一下

    重点介绍自己最满意的技术,言简意赅的说最近一段工作经历和项目,突出项目的贡献。

 2. 项目中用到的技术栈介绍一下

 3. 项目中做的认为比较满意的部分讲一讲?

提前梳理好,重点介绍自己熟悉和深入研究过的,特别注意,选择介绍在项目中有真实应用场景的技术,有实际落地的案例是加分项。有的人可能学了很多东西,但是具体一问都没动手实践过。

4. 如果这个技术方案让你重新设计,你怎么做?

这个需要在平时工作中就想好,不浮在表面,思考要有深度,你是否对自己负责的系统做过完整的梳理,把整个链路、模块之间的关系都摸清楚了。例如面试官可能会问你,你的系统能承受多大的QPS、一次业务请求在系统中的完整链路,某个模块的实现逻辑,让你重新设计有没有可能做到更好。

5. Java集合类都有哪些?平常用到哪些?

安琪拉画了如下这张图,我建议大家有时间看看 java.util 的源码,可以自己动手画一画,印象会更深刻。

20道Java高频面试题-题解_java_02

上面这个图梳理一下,为了方便对照上图看,图都放在最后

  • Java中的集合类可以分为两大类:一类是实现[Map接口];另一类是实现[Collection接口]

    实现Collection 接口的分为List 和Set,所以Java 集合类是由Map、List、Set构成。

  • Map 提供了键(key)值(value)对,定义了映射关系,如 图5.1所示,这个接口定义了Map需要的一些基本的方法,例如:size()、get()、put()。

    20道Java高频面试题-题解_java_03

    AbstractMap 是抽象类(接口和抽象类统一用虚线框),实现了一些基础的操作。类定义如下:

    public abstract class AbstractMap<K,V> implements Map<K,V>  K是key的泛型,V是value的泛型

    知识点: 经常面试被拿来比较的是HashMap 和HashTable。区别是HashTable 是线程安全的,实现方式是HashTable 会在一些需要操作hash表的地方加锁,具体方法就是在函数方法声明上加了’synchronized‘ 关键字,例如:put 和 get方法。

    知识点: WeakHashMap 基于WeakReference,

  •  

    • 强引用,例如 Object angela = new Object(), angela 就是强引用,对象只要还被引用,直到内存不足也不会被回收。

    • WeakReference,当一个对象被WeakReference引用, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收;

    • SoftReference和WeakReference一样, 但被GC回收的时候需要多一个条件: 当系统内存不足时才会回收SoftReference 引用的对象。

    • TreeMap 实现了SortedMap(实际上实现的是NavigableMap,NavigableMap继承了SortedMap),底层数据结构是红黑树,存放的元素都是按照key 来排序的,排序规则可以传入 Comparator 来定义。

    • WeakHashMap 有个特点,它里面的元素随时可能会变成null,它是基于WeakReference实现,举个例子:

      WeakHashMap hashMap = new WeakHashMap();
      for (int i = 0; ; i++) {
      //一直往里面加数据, 内存会爆炸
      hashMap.put(i, new String("angela"));
      
      //每隔一千次判断一下有没有对象被回收
      if (i % 10000 == 0) {
       //遍历一遍
       for (int j = 0; j < i; j++) {
         //
         if (hashMap.get(j) == null) {
           System.out.println("key为" + j + "的对象已经为null, GC会回收");
           return;
         }
       }
      }
      

      打印:

      key为54808的对象已经为null, GC会回收
      
    • HashMap 想必大家都知道是面试的重中之重,基本常规面试很容易被问

  • List 下有ArrayList 和LinkedList,Vector,Stack。

    知识点: ArrayList 和 LinkedList 区别,

    最大的区别: ArrayList 底层是基于数组的数据结构,LinkedList基于链表的数据结构;

    • Vector 和 ArrayList 区别是Vector 是线程安全的。

    • Stack 是栈,继承自 Vector,和Vector一样,基础数据结构都是数组,Stack使用数组实现先进后出,基本原理是push: 向数组尾部追加元素,pop是让数组尾部置空,设置元素为null。

      想详细了解Stack,可以参考我写的这篇: Java知识体系-Java集合栈

    • 数组和链表区别很明显,数组随机访问速度快,链表查找需要从表头开始找。理解什么是随机访问,就是给定一个随机的index(下标),希望找到元素,数组直接array[index] 访问,时间复杂度0(1), 链表是O(n),因为链表需要从头开始找。

    • 新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

  • Set 下有HashSet、TreeSet。Set最大的特点是key是唯一的,不允许重复。

    HashSet 也是基于HashMap实现的,只不过value 是固定值。

    TreeSet 是基于TreeMap实现,当然初始化的时候也可以指定基于自定义的NavigableMap实现。存放的元素是基于key排序好的。

    所以实际上搞懂HashMap 和 TreeMap,这二个Set 基本也清楚了。

6. ArrayList 和 LinkedList 区别?

最大的区别: ArrayList 底层是基于动态数组的数据结构,LinkedList基于链表的数据结构;

  • 数组和链表区别很明显,数组随机访问速度快,链表查找需要从表头开始找。理解什么是随机访问,就是给定一个随机的index(下标),希望找到元素,数组直接array[index] 访问,时间复杂度0(1), 链表是O(n)。

  • 新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

7. HashMap 实现的数据结构和扩容过程?

数据结构是数据+链表

8. ArrayList 和 LinkedList 区别?你平常怎么选择?

  • 比较多的随机访问,比如用下标index 访问集合,用ArrayList;

  • 如果有比较多的在集合中间插入和删除,用LinkedList,因为ArrayList中间每次插入、删除需要移动数组元素。

9. 异常类都有哪些?Exception 和 Error什么区别?

20道Java高频面试题-题解_链表_04

  • Error是错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

  • Exception 规定的异常是程序本身可以处理的异常。异常和错误的区别是,异常是可以被处理的,而错误是没法处理的。其中Exception 又有二类:

  • Checked Exception

    可检查的异常,这是编码时非常常用的,所有checked exception都是需要在代码中处理的。它们的发生是可以预测的,正常的一种情况,可以合理的处理。比如IOException,或者一些自定义的异常。除了RuntimeException及其子类以外,都是checked exception

  • Unchecked Exception

    RuntimeException及其子类都是unchecked exception。比如NPE空指针异常,除数为0的算数异常ArithmeticException等等,这种异常是运行时发生,无法预先捕捉处理的。Error也是unchecked exception,也是无法预先处理的。

10. Synchronized 原理,锁膨胀过程 ?

20道Java高频面试题-题解_数据结构_05图片

11. synchronized 和 Reentrantlock 区别?

大家看下面这段,重在理解

两者的共同点:

1. ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁

2. ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的
不可用性提供了更高的灵活性

3. ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的

二者的不同点:

    1. ReentrantLock 可以实现公平锁

    2. ReentrantLock 通过 Condition 可以绑定多个条件

    3. 底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,lock 是同步非阻塞,采用的是乐观并发策略

    4. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。

    5. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。

    6. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

    7. Lock 可以提高多个线程进行读操作的效率,既就是实现读写锁等

    作用:都用来协调多线程对共享资源的访问

    可重入性质:都是可重入锁,同一线程可以多次获得同一个锁

    锁性质:都保证了可见性和互斥性

    这道题总结解答出处:synchronized 和 ReentrantLock 区别是什么?

  1. 线程池原理是怎样的?

  2. 分布式事务一致性怎么实现?

    这个大家可以优先看自己系统的实现。

    我先来介绍一下分布式事务一致性的需求背景,我们经常使用支付工具转账,比如以经典的小明给小红转账100元为例,如果是单机系统,我们可以用本机事务轻松解决,但是分布式系统,转账可能是跨系统的调用,我们要保证数据的一致性就有些额外工作量了。

    现在普遍互联网分布式系统都不会维护实习强一致性,而是做最终一致性,允许有短暂的不一致。

    我这里只介绍用的很普遍的事务消息,其他的比如二阶段提交、本地消息表做数据一致性比对&补偿都是实现一致性的方式。

    事务消息发送步骤如下:

    20道Java高频面试题-题解_数据结构_06

    事务消息回查步骤如下:

    以上内容参考: RocketMQ事务消息

    1. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时间后服务端将对该消息发起消息回查。

    2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。

    3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行操作。

    4. 发送方将半事务消息(类似prepare消息),消息内容是将小红账号加100元,发送至消息队列RocketMQ版服务端。

    5. 消息队列RocketMQ版服务端将消息持久化成功之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事务消息。

    6. 发送方开始执行本地事务逻辑,讲小明账号扣减100块。

    7. 发送方根据本地事务执行结果向服务端提交二次确认(100块扣成功则Commit,100块扣失败则发Rollback),服务端收到Commit状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到Rollback状态则删除半事务消息,订阅方将不会接受该消息。

  3. 消息乱序遇到过吗?怎么解决的?

    消息重复业务系统幂等解决,消息乱序,如果是业务系统,一般是MQ尽量保障同一用户路由到同一消息分区,但这个只是尽量,一般消息乱序都是由下游消费方来处理,处理方法是消息中增加版本号、occurTime(业务时间发生时间)来判断消息的先后顺序,然后做对应的业务逻辑,例如,同一业务流水号,从库里面的数据的版本号或occurTime和新消息的版本号和occurTime 比较,版本号更大,时间更靠后的为最新消息,可以做更新操作。

    一般消息中间件都会遇到以下几个问题:

    • 消息重复

    • 消息并发

    • 消息乱序

    • 消息延迟

    • 消息积压

 

  • ThreadLocal 用过吗?实现机制?

    发现很多博客关于ThreadLocal的说明写错了,ThreadLocal不是维护了key为Thread对象的Map,而是Thread对象维护了一个key为ThreadLocal 对象的Map。

    参考之前写的:一文了解ThreadLocal用法

  • wait、sleep区别?

    1、sleep是线程中的方法,但是wait是Object中的方法。

    2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

    3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

    4、sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要(不指定时间需要被别人中断)。

  • 反射用过吗?什么原理?

    参考这篇: 你不得不知道的反射(非常重要)

  • 动态代理了解吗?

    参考之前写的:JAVA动态代理

  • 单例模式了解吗?实现一个线程安全的单例模式?

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    注意:

    实现代码(只列举双重检查锁的写法,其他写法参考):

    20道Java高频面试题-题解_数据结构_07

  • 1、单例类只能有一个实例。

  • 2、单例类必须自己创建自己的唯一实例。

  • 3、单例类必须给所有其他对象提供这一实例。

  • 无界队列和有界队列?

先说概念:

有界队列

有界队列:就是有固定大小的队列。比如设定了固定大小的 LinkedBlockingQueue,又或者大小为 0,只是在生产者和消费者中做中转用的 SynchronousQueue。

无界队列

无界队列:指的是没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出。当然现实使用中,几乎不会有到这么大的容量(超过 Integer.MAX_VALUE),所以从使用者的体验上,就相当于 “无界”。比如没有设定固定大小的 LinkedBlockingQueue。

无界队列的特性:所以无界队列的特点就是可以一直入列,不存在队列满负荷的现象。这个特性,在我们自定义线程池的使用中非常容易出错。而出错的根本原因是对线程池内部原理的不了解。

使用无界队列创建了一个线程池如下:

ExecutorService executor =  new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

这里的队列都指线程池使用的阻塞队列BlockingQueue的实现,使用的最多的应该是LinkedBlockingQueue,注意一般情况下要配置一下队列大小,设置成有界队列,否则JVM内存会被撑爆!

来一张队列全体照

20道Java高频面试题-题解_链表_08

1.常见的有界队列

  • ArrayBlockingQueue:基于数组实现的阻塞队列;

  • LinkedBlockingQueue:基于链表实现的阻塞队列,该有界队列不设置大小时就是Integer.MAX_VALUE;

  • SynchronousQueue:内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时使用CAS代替了原来的锁逻辑;

上面三个队列的共性:

  • put和take 操作都是阻塞的

  • offer和poll 操作不是阻塞的,offer操作时,若队列满了会返回false,不会阻塞;poll操作时,若队列为空会返回null,不会阻塞;

  • 并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用CPU,在有些场景也是需要阻塞的,put和take操作存在必有其存在的必然性;

ArrayBlockingQueue 与 LinkedBlockingQueue 对比:

  • ArrayBlockingQueue 实现简单,表现稳定,添加和删除操作使用同一个锁,通常性能不如后者;

  • LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些;

2.常见的无界队列

  • ConcurrentLinkedQueue:无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常;

  • PriorityBlockingQueue:具有优先级的阻塞队列;

  • DelayedQueue:延时队列,使用场景

  • - 缓存:清掉缓存中超时的缓存数据;

  • 任务超时处理;

  • 内部实现其实是采用带时间的优先队列,可重入锁,优化阻塞通知的线程元素leader

  • LinkedTransferQueue:简单的说也是进行线程间数据交换的利器

无界队列的共性:

  • put 操作永远都不会阻塞,空间限制来源于系统资源的限制;

  • 底层都使用CAS无锁编程;