如果懂JMM,这个压根就不用看。
CAS(Compare And Swap)
其保留有3个值
V(内存值)
、A(旧的预期值/旧值)
、B(要修改的值/新值)
。
V指的是主存中的存在的值;A指的是每个线程自己持有的V的副本;B是经过一系列自己指定的运算后的结果。
当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false
设定:
1.内存中有个空间(Q)存储了一个值为1,我们使用多线程且使用CAS进行修改会发生如下反应。
2.有2个线程(A和B)同时对Q进行 +1 操作。(可以参考JDK 1.7中AtomicInteger
)
// JDK 1.7 中的代码示例
private volatile int value;
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final int get() {
return value;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
根据JMM可以得知,2个线程会各自获取一份Q的副本(值为1)。
1.线程A开始运行,current获取到线程中的副本值为1,然后执行完成+1操作,此时线程切换给B操作。
2.线程B开始运行,current获取到线程中的副本值为1,然后执行完成+1操作,`之后拿着线程B中Q的副本[current]与Q的值进行比较,如果两值相等就将计算出来的next进行替换掉Q的原值(CAS操作)`,此时线程切换给A操作。
3.线程A开始运行,拿着线程A中Q的副本进行相同的比较与赋值操作(CAS操作),在V与A的比较中发现不对等(**内存中的Q值为2,线程B的副本值为1**),于是认为value正在被另外一个线程操作,所以不能进行值替换。**
4.这个时候线程A会再一次取Q中的值作为新的副本,再次进行+1操作并通过CAS尝试写入新值,如果一直写不进去这个过程会不断重复,直到能成功的执行为止。
根据代码可以看出这个
value
的volatile修饰符
十分重要,当操作失败后能及时的获取到其他线程对其的修改值。
如果去掉volatile修饰符
,那不同线程之间就完全感知不到对方对值的修改,也就是说在第4步的时候,线程A永远取不到Q的新值。
至于compareAndSet方法
,我们也能从示例代码中清楚的看到是调用了Unsafe这个类中的方法。
Unsafe提供了硬件级别的原子操作,但不建议自己使用。
CAS的ABA问题
在上诉基础上,多添加几个线程进行对Q的操作,要求是加减都有。
那线程中CAS操作会不会存在问题?
线程L的副本存储的为1,我们不能确保在线程L中进行V和A的比较操作之前,是否有其他线程将Q的值改为2后又改回了1。
这就是ABA问题,当然可以采用版本号等方式解决。
ABA问题的例子:
主存中的V的值为1。
线程A中副本值为1,然后进行+1操作,线程切换。
线程B中的副本之为1,然后进行+1操作,最后通过CAS对V的值进行写入,现在V的值为2,线程切换。
线程C中的副本之为2,然后进行-1操作,最后通过CAS对V的值进行写入,现在V的值为1,线程切换。
线程A之前已经计算完了,现在尝试进行用CAS操作,发现V和自己的副本值相同后进行写入操作。
虽然看起来没什么问题,但可能在某些逻辑中就会导致潜在问题的出现。
额外补充
观看其他的java.util.concurrent中的代码不难发现,该包是基于CAS的构建的,而CAS又依赖于Unsafe类。