乐趣区

关于cas:Java中CAS原理分析volatile和synchronized浅析

CAS 是什么?


CAS 英文解释是比拟和替换,是 cpu 底层的源语,是解决共享变量原子性实现计划,它定义了三个变量,内存地址值对应 V,期待值 E 和要批改的值 U,如下图所示,这些变量都是在高速缓存中的,如果两个线程 A,B 别离通过 cas 形式同时批改共享变量,假如当 A 线程先获取工夫片,如果发现 V 的值和 E 相等就将主内存值更新为 U,如果不相等阐明线程 B 在线程 A 更新之前曾经胜利更新过,线程 A 会失败重试,此时依据缓存一致性协定,线程 A 的本地正本会生效,须要从主内存再同步最新的变量到本地内存正本,在 Java 中通过调用 UnSafe 的 compareAndSet 相似形式调用,底层是 c,反编译后操作系统指令是 cmpxchg 指令。

保障 i ++ 原子性


你肯定会有一个疑难,被 volatile 润饰的变量 i,i++ 为什么会有线程平安问题呢,也就是原子性的问题,咱们还是举一个经典的 i ++ 案例一步步剖析吧!咱们晓得在多线程状况下 volatile 保障了共享变量的可见性,程序行,但唯独不能保障原子性,起因是 i ++ 是一个复合操作,大抵能够分成 3 步,1. 先从主内存拿到最新的 i 值,2. 将 i 加 1 这个操作保留到操作数栈,3. 从栈中取出 i 加 1 的值写回到主内存。OK,当线程 AB 同时执行 i ++ 操作时,比方线程 A 先获取工夫片,执行完第 2 步,这是线程 A 还未执行完,工夫片调配给线程 B,B 顺利执行完所有操作后并同步了主内存,假如咱们 i 的初始值是 1,那么此时主内存值是 2,因为线程 B 执行结束,cpu 工夫片又回到线程 A 手上,做第 3 步操作,此时同步到主内存的值还是 2,看,线程 A,B 各做了一次加 1 的操作,但最终后果可能是 2,cas 的作用就来了,他能保障 i ++ 操作的原子性,为什么能保障原子性呢?cas 能够把下面三个操作合并成一个操作,是原子的。

有什么益处?


大家都晓得解决多线程平安须要用到锁的,能够用 synchronized 来解决,然而 synchronized 也有它的劣势,最次要是它是阻塞的,阻塞会有什么问题?性能啊,这是计算机人不能忍的,频繁内核外核切换,会重大节约系统资源,所以就提了 cas 这个乐观锁概念,它是非阻塞的,操作系统不必在内核态与用户态来回切换,相当于用 while 循环形式获取锁,在性能上有肯定晋升。即便这样,也会有肯定问题,上面咱们来看看。

有什么问题?


1.ABA 问题。

这个案例比较简单,线程 A 把共享变量 i,从 1 变成 2,再变成 1,线程 B 想把 i 变成 2,原本应该是不会胜利,因为即时变量 i 当初是 1,然而它的状态变动了,他的解决方案是版本号。相当于批改胜利一次版本号减少 1,就能够解决了,已经被面试官问到一个问题,cas 是线程平安的吗?答案不是线程平安的。

2. 自旋工夫过长。

如果一个线程拿到锁后,始终不开释,其余线程就只能始终循环期待。

3. 只能保障一个共享变量的原子性。

像 Automic 包上面的基本上都只能保障一个变量的原子性。

JUC 包上面应用!


可能有些童鞋看 JDK 源码会比拟纠结一个点,发现 volatile 关键字个别都会和 cas 连用,如果不要 volatile 会怎么样呢 ?cas 自身只作用于办法,cas 对共享变量没有束缚,如果不对共享变量做 volatile 润饰,是不可见的,不可能保障共享变量的实效性,须要期待共享变量被动同步到主内存,这是须要花工夫的,效率更低下,所有在 JUC 并发包里始终能够看到这样的volatile 关键字个别都会和 cas 组合。

总结


这篇文章,咱们先引出了 cas 概念,并且阐明了它的优缺点,做了案例介绍,简略的和 synchronized 关键字做了比拟,最初,深刻的阐明了 volatile 关键字cas 连用的效率,这是我在深刻思考后失去的论断,分享给大家,文章有肯定浏览门槛,如果有想搞清楚童鞋,能够 1v1 私聊探讨交换。心愿大家喜爱。点赞哦!

我是叫练,边叫边练,欢送点赞和评论。

退出移动版