多线程之CAS
多线程之CAS
CAS
CAS(Compare And Swap) 是乐观锁的一种实现方式。
- 内存位置 (V)
- 预期原值 (A)
- 新值 (B)
当且仅当预期值A与内存值V相同,将内存值修改为B,否则什么都不做。
AtomicInteger
- getAndIncrement
- getAndDecrement
- incrementAndGet
- decrementAndGet
Java Atomic 包 使用乐观锁中的CAS。
eg: AtomicInteger#getAndIncrement
// setup to use Unsafe.compareAndSwapInt for updates |
Unsafe提供了一些对底层的操作,通过JDI(Java Native Interface) 本地调用C/C++语言来实现CAS操作的。
do while循环,从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
CAS的问题:
- 多线程场景下会出现ABA问题
- 循环(自旋)时间长开销大,如果CAS操作失败,就需要循环进行CAS操作(循环同时将期望值更新为最新的),如果长时间都不成功的话,那么会造成CPU极大的开销。
- 只能保证一个共享变量的原子操作, 如果需要对多个共享变量进行操作,可以使用加锁方式(悲观锁)保证原子性,或者可以把多个共享变量合并成一个共享变量进行CAS操作。
ABA问题
有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
- 线程1,期望值为A,欲更新的值为B
- 线程2,期望值为A,欲更新的值为B
线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。
ABA解决问题:
添加版本号,在变量前面加上版本号,每次变量更新的时候变量的版本号都+1
,即A->B->A
就变成了1A->2B->3A
。
AtomicStampedReference 类使用版本号作比较的CAS机制。
CAS VS Synchronized
性能差别: synchronized属于悲观锁,没有获得锁的线程会进入blocked状态,然后获取锁后恢复为runnable,涉及到用户模式和内核模式的转换,代价较高。
Reference
https://mp.weixin.qq.com/s/f9PYMnpAgS1gAQYPDuCq-w
https://mp.weixin.qq.com/s/nRnQKhiSUrDKu3mz3vItWg
https://mp.weixin.qq.com/s/yuokP0GtPRFc5UWxecrcJg