梦想只要能持久,就能成为现实。我们不就是生活在梦想中的吗?
并发 List 和 Map 是技术面时常问的问题,问的问题也都比较深入,有很多问题都是面试官自创的,市面上找不到,所以说通过背题的方式,这一关大部分是过不了的,只有我们真正理解了 API 内部的实现,阅读过源码,才能自如应对各种类型的面试题,接着我们来看一下并发 List、Map 源码相关的面试题集。
答:相同点:底层的数据结构是相同的,都是数组的数据结构,提供出来的 API 都是对数组结构进行操作,让我们更好的使用。
不同点:后者是线程安全的,在多线程环境下使用,无需加锁,可直接使用。
答:主要有:1. 数组容器被 volatile 关键字修饰,保证了数组内存地址被任意线程修改后,都会通知到其他线程;
通过以上三点保证了线程安全。
答:的确,对数组进行加锁后,能够保证同一时刻,只有一个线程能对数组进行 add,在同单核 CPU 下的多线程环境下肯定没有问题,但我们现在的机器都是多核 CPU,如果我们不通过复制拷贝新建数组,修改原数组容器的内存地址的话,是无法触发 volatile 可见性效果的,那么其他 CPU 下的线程就无法感知数组原来已经被修改了,就会引发多核 CPU 下的线程安全问题。
假设我们不复制拷贝,而是在原来数组上直接修改值,数组的内存地址就不会变,而数组被 volatile 修饰时,必须当数组的内存地址变更时,才能及时的通知到其他线程,内存地址不变,仅仅是数组元素值发生变化时,是无法把数组元素值发生变动的事实,通知到其它线程的。
答:主要有:
答:主要是因为 CopyOnWriteArrayList 每次操作时,都会产生新的数组,而迭代时,持有的仍然是老数组的引用,所以我们说的数组结构变动,是用新数组替换了老数组,老数组的结构并没有发生变化,所以不会抛出异常了。
答:ArrayList 只需拷贝一次,假设插入的位置是 2,只需要把位置 2 (包含 2)后面的数据都往后移动一位即可,所以拷贝一次。
CopyOnWriteArrayList 拷贝两次,因为 CopyOnWriteArrayList 多了把老数组的数据拷贝到新数组上这一步,可能有的同学会想到这种方式:先把老数组拷贝到新数组,再把 2 后面的数据往后移动一位,这的确是一种拷贝的方式,但 CopyOnWriteArrayList 底层实现更加灵活,而是:把老数组 0 到 2 的数据拷贝到新数组上,预留出新数组 2 的位置,再把老数组 3~ 最后的数据拷贝到新数组上,这种拷贝方式可以减少我们拷贝的数据,虽然是两次拷贝,但拷贝的数据却仍然是老数组的大小,设计的非常巧妙。
答:相同点:1. 都是数组 + 链表 +红黑树的数据结构,所以基本操作的思想相同;
不同点:1. ConcurrentHashMap 是线程安全的,在多线程环境下,无需加锁,可直接使用;
答:主要有以下几点:
答:CAS 其实是一种乐观锁,一般有三个值,分别为:赋值对象,原值,新值,在执行的时候,会先判断内存中的值是否和原值相等,相等的话把新值赋值给对象,否则赋值失败,整个过程都是原子性操作,没有线程安全问题。
ConcurrentHashMap 的 put 方法中,有使用到 CAS ,是结合无限 for 循环一起使用的,步骤如下:
可以看到这样做的好处,第一是不会盲目的覆盖原值,第二是一定可以赋值成功。
答:ConcurrentHashMap 新增了一个节点类型,叫做转移节点,当我们发现当前槽点是转移节点时(转移节点的 hash 值是 -1),即表示 Map 正在进行扩容。
答:无限 for 循环,或者走到扩容方法中去,帮助扩容,一直等待扩容完成之后,再执行 put 操作。
答:区别很大,HashMap 是直接在老数据上面进行扩容,多线程环境下,会有线程安全的问题,而 ConcurrentHashMap 就不太一样,扩容过程是这样的:
简单来说,通过扩容时给槽点加锁,和发现槽点正在扩容就等待的策略,保证了 ConcurrentHashMap 可以慢慢一个一个槽点的转移,保证了扩容时的线程安全,转移节点比较重要,平时问的人也比较多。
答:非常不一样,拿 put 方法为例,Java 7 的做法是:
Java 7 的做法比较简单,缺点也很明显,就是当我们需要 put 数据时,我们会锁住改该数据对应的某一段,这一段数据可能会有很多,比如我只想 put 一个值,锁住的却是一段数据,导致这一段的其他数据都不能进行写入操作,大大的降低了并发性的效率。Java 8 解决了这个问题,从锁住某一段,修改成锁住某一个槽点,提高了并发效率。
不仅仅是 put,删除也是,仅仅是锁住当前槽点,缩小了锁的范围,增大了效率。
因为目前大多数公司都已经在使用 Java 8 了,所以大部分面试内容还是以 Java 8 的 API 为主,特别是 CopyOnWriteArrayList 和 ConcurrentHashMap 两个 API,文章毕竟篇幅有限,建议大家多多阅读剩余源码。