共享模型之无锁
概述
本章内容包括以下部分
CAS与volatile
原子整数
原子引用
原子累加器
Unsafe
线程安全解决方法
有如下需求,保证account.withdraw取款方法的线程安全
![](https://i-blog.csdnimg.cn/blog_migrate/92904e273dce96d3a93beeadba2fcb6e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0a923b6fd4969f8c7040c165866107ef.png)
线程不安全的原因
![](https://i-blog.csdnimg.cn/blog_migrate/1259cc07c04e2e805c4c34828060cd43.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f47c79e85be5279c433dde4cd844e6ad.png)
单核的指令交错
多核的指令交错
解决思路-锁
![](https://i-blog.csdnimg.cn/blog_migrate/4b0a10fa421f859de8a783af478f7d2a.png)
解决思路-无锁
![](https://i-blog.csdnimg.cn/blog_migrate/22b54a7b2b5e954ac1564d8056dc50d8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/85d3928b2a339742b4bc64f34d0c1ee3.png)
CAS与volatile
AtomicInteger解决线程安全问题的原理
![](https://i-blog.csdnimg.cn/blog_migrate/a945ffaa855bb3c72a44b86a24dad073.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4b9707d5175b6cde1ceb6e2f85a3cff2.png)
其中的关键是compareAndSet,它的简称就是CAS(也有Compare And Swap的说法),它必须是原子操作。
![](https://i-blog.csdnimg.cn/blog_migrate/f0e360f5c97c5dadec4d5080cab38990.png)
慢动作分析
![](https://i-blog.csdnimg.cn/blog_migrate/4c0b46674a173cb4d65f11934ffc1a0d.png)
输出结果
![](https://i-blog.csdnimg.cn/blog_migrate/cb43dfd2cb602b4fa4c0f06b430597cf.png)
volatile
获取共享变量时,为了保证该变量的可见性,需要使用volatile 修饰。
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取
它的值,线程操作volatile 变量都是直接操作主存。即一个线程对volatile 变量的修改,对另一个线程可见。
为什么无锁效率高
无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻
线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速... 恢复到高速运行,代价比较大
但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
![](https://i-blog.csdnimg.cn/blog_migrate/e76b0f05f11b6c1bb0202803a07f8429.png)
CAS的特点
结合CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
原子整数
J.U.C 并发包提供了:
AtomicBoolean
AtomicInteger
AtomicLong
以 AtomicInteger 为例
![](https://i-blog.csdnimg.cn/blog_migrate/6ca9957e525f12d250acacaac4cff36d.png)
原子引用
为什么需要原子引用类型?
AtomicReference
AtomicMarkableReference
AtomicStampedReference
![](https://i-blog.csdnimg.cn/blog_migrate/3b63e86bd406f911931804b45a8c657f.png)
不安全实现
![](https://i-blog.csdnimg.cn/blog_migrate/7a1e1a540688a60921c0e695125ad7d1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/82f9a15197901720f2207a21b7af223f.png)
安全实现-使用锁
![](https://i-blog.csdnimg.cn/blog_migrate/6011e2a1097e386e6381d479948ceb8f.png)
安全使用-使用CAS
![](https://i-blog.csdnimg.cn/blog_migrate/25cf2dc16e407621abb98c4c9745791c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2c3de427f24e9243fde6d0c7f4483394.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8a774e3046f243c014292fd821fb5508.png)
ABA问题及解决
ABA问题
![](https://i-blog.csdnimg.cn/blog_migrate/732c634426e06c6803cf9f19f8acc0ea.png)
![](https://i-blog.csdnimg.cn/blog_migrate/16aa58e9c14ddb886527be80dc0da069.png)
主线程仅能判断出共享变量的值与最初值A 是否相同,不能感知到这种从 A 改为 B 又改回 A 的情况,如果主线程希望:
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号AtomicStampedReference
![](https://i-blog.csdnimg.cn/blog_migrate/f1643c032fbf20377f191b7686bbfbf5.png)
输出为
![](https://i-blog.csdnimg.cn/blog_migrate/34e31013a6cc590e90020c0d581d4151.png)
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如:A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference
![](https://i-blog.csdnimg.cn/blog_migrate/0ed36846058ec8900d58cabe769aa416.png)
AtomicMarkableReference
![](https://i-blog.csdnimg.cn/blog_migrate/42b38b6d0e002d0fb0b363b6f428211b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6c2ce6a7e1145e459b6f42139f31224d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/54f894c255fb147a1bccc7b8d252d67b.png)
原子数组
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
![](https://i-blog.csdnimg.cn/blog_migrate/91ea9ba8c14031fbb347ae498000c7da.png)
字段更新器
AtomicReferenceFieldUpdater // 域 字段
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常
![](https://i-blog.csdnimg.cn/blog_migrate/3efe44b0d3d8daa45cee348d4ec0f986.png)
![](https://i-blog.csdnimg.cn/blog_migrate/09a299c5481ca16803771a3460857724.png)
![](https://i-blog.csdnimg.cn/blog_migrate/64b73afd66de18bd73bdacc0107caabf.png)
原子累加器
累加器性能比较