开篇介绍面试
你们好,公众号【Java极客思惟】近期会整理一些Java高频面试题分享给小伙伴,也但愿看到的小伙伴在找工做过程当中可以用获得!本章节主要针对Java一些多线程高频面试题进行分享。算法
通知:公众号【Java极客思惟】正在送书福利活动,关注公众号并参加福利活动吧!只有参与了本次活动的小伙伴才可以参与年末的大福利,不要错过呀~缓存
Q1:微信
什么是CAS算法?数据结构
CAS(compare and swap)的缩写。多线程
Java利用CPU的CAS指令,同时借助JNI来完成对Java的非阻塞算法,实现原子操做(其实就是自旋操做,不断循环,直到成功)。其它原子操做都是利用相似的特性来完成的。并发
CAS有三个关键操做值:内存值V、 预期值A 、 要修改的值B 。app
当且仅当 预期值A 和 内存值V 一致时,才会将 内存值V 内容修改成 B,不然将什么都不作。框架
CAS的缺点也很明显:工具
在并发量比较高的状况下,若是许多线程反复尝试更新某一个变量,可是却又一直更新不成功,一直在循环(自旋),那么会给CPU带来很大的压力。
CAS机制所保证的只是一个变量的原子性操做,而不能保证整个代码块的原子性。好比须要保证3个变量共同进行原子性的更新,这样就不得不使用synchronized关键字进行同步操做了。
好比线程A端了一杯水放在桌子上,可是被其余事调度走了,而且释放了锁,此时线程B通过,看到桌子上的水,端起来喝了半杯,而后又给打满一杯水放在桌子上。此时虽然仍是一杯水,可是杯中的水再也不是原来的那杯水了,而线程A也忙完了,回头来看到桌子上仍是一杯水,可是不知道水已经被替换过了。这就是典型的ABA问题,还有不少相似的场景。这种状况对依赖过程值的情景的运算结果影响很大。这是CAS机制最大的问题所在。
CPU开销过大
不能保证代码块的原子性
ABA问题
Q2:
什么是AQS?
AQS(AbstractQueuedSynchronizer)
AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计做为一些可用原子int值来表示状态的同步器的基类。
若是有看过相似CountDownLatch类的源码实现,会发现其内部有一个继承了AbstractQueuedSynchronizer的内部类Sync。可见CountDownLatch是基于AQS框架来实现的一个同步器。相似的同步器在JUC下还有很多(好比Semaphore)。
AQS的核心思想是基于volatile int state 这样的volatile变量,配合Unsafe工具对其原子性的操做来实现对当前锁状态进行修改。同步器内部依赖一个FIFO的双向队列来完成资源获取线程的排队工做。
AQS中的数据结构 - 节点和同步队列
节点加入到同步队列
首节点变化
Q3:
volatile关键字有什么用?
Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会当即被更新到主内存中,当有其余线程须要读取时,它会去内存中读取新值。主要的原理是使用了内存指令。
LoadLoad重排序:一个处理器先执行一个L1读操做,再执行一个L2读操做;可是另一个处理器看到的是先L2再L1;
StoreStore重排序:一个处理器先执行一个W1写操做,再执行一个W2写操做;可是另一个处理器看到的是先W2再W1;
LoadStore重排序:一个处理器先执行一个L1读操做,再执行一个W2写操做;可是另一个处理器看到的是先W2再L1;
StoreLoad重排序:一个处理器先执行一个W1写操做,再执行一个L2读操做;可是另一个处理器看到的是先L2再W1。
Q4:
描述一下volatile关键字对原子性、可见性以及有序性是如何保证的?
在volatile变量写操做的前面会加入一个Release屏障,而后再以后会加入一个Store屏障,这样就能够保证volatile写跟Release屏障以前的任何读写操做都不会指令重排,而后Store屏障保证了,写完数据以后,立马会执行flush处理器缓存的操做。
在volatile变量读操做的前面会加入一个Load屏障,这样就能够保证对这个变量的读取时,若是被别的处理器修改过了,必须得从其余处理器的高速缓存(或者主内存)中加载到本身本地高速缓存里,保证读到的是最新数据;在以后会加入一个Acquire屏障,禁止volatile读操做以后的任何读写操做会跟volatile读指令重排序。
与volatile读写内存屏障对比一下,是相似的意思。
Acquire屏障 其实就是 LoadLoad屏障 + LoadStore屏障;
Release屏障 其实就是 StoreLoad屏障 + StoreStore屏障。
Q5:
简述一下synchronized关键字的原理是什么?
synchronized是由JVM实现的一种实现互斥同步的方式,查看被synchronized关键字修饰过的程序块编译后的字节码会发现:被synchronized修饰过的程序块,在编译先后被编译器生成了monitorenter 和 monitorexit 两个字节码指令。
在虚拟机执行到 monitorenter 指令时,首先会尝试获取对象的锁:若是这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器 + 1;
当执行 monitorexit 指令时,将锁计数器 - 1;当计数器为0时,锁就被释放了。若是获取对象失败了,那当前线程就要阻塞等待,知道对象锁被另一个线程释放为止。
Q6:
CountDownLatch 和 CyclicBarrier的区别?
CountDownLatch的计数器只能使用一次。而CyclicBarrierd的计数器可使用reset()方法进行重置。因此CyclicBarrier能处理更为复杂的业务场景。好比若是计算发生错误,能够重置计数器,并让线程们从新计算一次。
CyclicBarrier还提供其余有用的方法,好比getNumberWaiting()方法能够得到CyclicBarrier阻塞的线程数量。isBroken() 方法能够用来知道阻塞的线程是否被中断。
CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
CountDownLatch依靠一个外力(计数器、发令枪)来控制线程,而CyclicBarrier是至关于用自己来控制线程。举个例子:
CountDownLatch:有一个装满宝石的房间,门外有7把锁,而后有7我的要进入房间组成队伍A,还有另外7我的手里拿着钥匙组成队伍B,那么首先A组的7我的必须等到B组的7我的把钥匙送过来,而后把门外的7把锁分别打开以后,这A组的7我的才可以进入房间拿到宝石。
CyclicBarrier:有一个装满宝石的房间,门外也有7把锁,而后7我的必须到另外的一个房间,各自完成一个任务以后,这7我的才可以各自得到一把锁,完成任务以后,这7我的就可以打开那7把锁,进房间拿到宝石。
明天,会介绍多线程一些深刻的知识,长按二维码关注我吧~
祝你们都能拿到心仪的offer!
若是以为文章不错,欢迎关注、点赞、收藏,大家的支持是我创做的动力,感谢你们。
若是文章写的有问题,请不要吝啬,欢迎留言指出,我会及时核查修改。
若是你还想更加深刻的了解我,能够微信搜索「Java极客思惟」进行关注。天天8:00准时推送技术文章,让你的上班路不在孤独,并且每个月还有送书活动,助你提高硬实力!