什么是 CAS
CAS(compare and swap),比较与交换。
- 如果普通的比较与交换
public void compareAndSwap(int num1, int num2) {
if (num1 == 10) {
num2 = 20;
}
}
这个代码在 被编译成字节码文件等待一些列操作后,将在 CPU 上执行,而代码翻译成的指令不止一条,那就不能保证原子性。
- 而在 CPU 中,能用一条指令能够成比较与交换。(伪代码)
// 如果在这个位置(address) 的值等于 这个值(expectedValue),那么交换(newValue)。
boolean CAS(address,expectedValue,newValue) {
if(address 的 value == expectedValue) {
address 的 value = newValue;
return true;
}
}
至于这个一条指令完成比较与交换和自旋锁有什么关系呢?下面解析!
CAS 原理
CAS 实现自旋锁
自旋锁:线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度。但经 过测算,实际的生活中,大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。基于这个 事实,轻量级锁 / 自旋锁诞生了。也就是说,当某个线程抢占 CPU 失败后,就会继续抢占。
举个栗子
A 今天给女神表白,女神拒绝了,于是 A 明天又来,天天来天天来,直到女神答应他为止。
先看实现的原理(伪代码)
class Lock {
// 如果在这个位置(address) 的值等于 这个值(expectedValue),那么交换(newValue)。
boolean CAS(address,expectedValue,newValue) {
if(address 的 value == expectedValue) {
address 的 value = newValue;
return true;
}
}
// 记录当前锁是哪个线程持有的, 如果为 null, 那么就表示当前锁没有被持有
Thread owner = null;
void Lock() {
// 循环判断, 如果锁(owner)没有被持有
// 也就是 owner == null
// 那么将 Thread.currentThread() 替换上去
// 结束循环, 如果 owner != null, 继续循环
while (!CAS(owner,null,Thread.currentThread()) {
// 没有内容
}
}
}
这就是基本的实现原理
如果不使用 CAS,而是用普通的判断,那么比较交换过程中产生的不止一条指令,如果中途被调度出 CPU,那么就无法完成自旋锁了。
就比如如果使用 两条指令,比较与交换,当比较发现当前锁没有持有线程的时候,正准备交换,突然,被调度出 CPU 了,其他线程占有了这个锁,此时预期的线程就没法占用这个锁。
CAS 实现原子类
由于 CAS 的操作只有一个指令,因此能保证原子性。
普通的什么 count++ 等就不是原子的,因此多个线程想要对一个数进行操作的时候,可能就会导致线程不安全问题。
在 Java 标准款中存在一些原子类,比如描述整数的 AtomicInteger
(int),
之所以 return …+1,是因为下面返回的是刚开始内存的值,类似于 count++
,如果是 ++count
,那么就是直接return
例如两个线程操作一个数
就可以保证线程的安全。
ABA 问题
- 不出现问题
线程 1 准备给 count - 50
线程 2 给count + 50,再 count - 50
最终结果是没错的,这就是 ABA 问题的大概样子,用官方话来说就是
ABA 的问题:就是一个值从 A 变成了 B 又变成了 A,而这个期间线程不清楚这个过程。
- 虽然这个样子看起来没错,但是还是会有意外的。
一个通俗点的例子
- 不出现问题的情况
A 微信余额有 100块, A 给 B 转账;
第一次转账 50 卡主了,没成功,此时微信余额还剩 100;
然后重新转,成功了。此时微信余额还剩 50;
当转账 A 的任务继续执行的时候,发现余额已经变了,那么就不执行了。- 出现问题的情况
A 微信余额有 100块, A 给 B 转账;
第一次转账 50 卡主了,没成功,此时微信余额还剩 100;
然后重新转,成功了。此时微信余额还剩 50;
此时 C 又给 A 转账 50,此时微信余额为 100;
当转账 A 的任务继续执行的时候,发现余额没变,那么就继续执行 ,此时微信余额 50;
实际上微信余额应该是 100 才对(100 - 50 + 50)
如何解决 ABA
给需要修改的内容增加一个属性,该属性记录该内容修改的时间(或者修改的次数也可以)