乐趣区

关于java:Java面试专题多线程3原子操作

开篇介绍

大家好,公众号【Java 极客思维】近期会整顿一些 Java 高频面试题分享给小伙伴,也心愿看到的小伙伴在找工作过程中可能用失去!本章节次要针对 Java 一些 多线程 高频面试题进行分享。

告诉:公众号【Java 极客思维】正在送书福利流动,关注公众号并加入福利流动吧!只有参加了本次流动的小伙伴才可能参加年底的大福利,不要错过呀~

Q1:

什么是 CAS 算法?

CAS(compare and swap)的缩写。

        Java 利用 CPU 的 CAS 指令,同时借助 JNI 来实现对 Java 的非阻塞算法,实现原子操作(其实就是自旋操作,一直循环,直到胜利)。其它原子操作都是利用相似的个性来实现的。

CAS 有三个要害操作值:内存值 V 预期值 A 要批改的值 B

当且仅当 预期值 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 准时推送技术文章,让你的下班路不在孤单,而且每月还有送书流动,助你晋升硬实力!

退出移动版