CAS详解
- synchronized关键字与Lock等锁机制都是悲观锁:无论做何种操作,首先都需要上锁,接下来再去执行后续操作,从而确保了接下来所有的操作都是由当前线程来执行的。
- 乐观锁:线程在操作之前不做任何预先的处理,而是直接去执行;当在最后执行变量更新的时候,当前线程需要有一种机制来确保当前被操作的变量是没有被其他线程修改的;CAS是乐观锁的一种极为重要的实现方式。
CAS(Compare And Swap)
比较与交换:这是一个不断循环的过程,一直到变量值被修改成功为止。CAS本身是由硬件指令来提供支持的,换句话说,硬件中通过一个原子指令来实现比较与交换;因此,CAS可以确保操作的原子性。
对于CAS来说,其操作数只要涉及到如下三个:
- 需要被操作的内存值V
- 需要进行比较的值A
- 需要进行写入的值B
只有当V==A的时候,CAS才会通过原子操作的手段将V的值更新为B。
透过字节码分析变量操作的原子性
package com.learn.thread.cas;
public class CASTest {
private int count;
public int getCount() {
return count;
}
// 读取 -> 修改 -> 写入:这三个操作并非原子操作
public void increase() {
++this.count;
}
}
javap获取字节码
// access flags 0x1
public increase()V
L0
LINENUMBER 12 L0
ALOAD 0
DUP
GETFIELD com/learn/thread/cas/CASTest.count : I // 读取,压入栈顶
ICONST_1 // 将1压入栈顶
IADD // 将栈顶与栈顶下面的值相加
PUTFIELD com/learn/thread/cas/CASTest.count : I // 将值赋给count
L1
LINENUMBER 13 L1
RETURN
L2
LOCALVARIABLE this Lcom/learn/thread/cas/CASTest; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
使用synchronized关键字
package com.learn.thread.cas;
public class CASTest {
private int count;
// 保证可见性,其他线程读到count为最新值
public synchronized int getCount() {
return count;
}
public synchronized void increase() {
++this.count;
}
}
public synchronized void increase();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field count:I
5: iconst_1
6: iadd
7: putfield #2 // Field count:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/learn/thread/cas/CASTest;
使用synchronized并非最佳选择。
使用并发包中的原子类
package com.learn.thread.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
// 获取当前atomicInteger的值:5
System.out.println(atomicInteger.get());
// 返回旧值,将当前值设为8:5
System.out.println(atomicInteger.getAndSet(8));
// 获取当前atomicInteger的值:8
System.out.println(atomicInteger.get());
// 返回旧值,将当前值自增1:8
System.out.println(atomicInteger.getAndIncrement());
// 获取当前atomicInteger的值:9
System.out.println(atomicInteger.get());
}
}
CAS底层实现
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
// var1: 待操作对象的引用
// var2:要操作的变量在当前对象中的内存偏移量
// var5:变量的预期值A
// var4:即将要写入的新的值
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
关于CAS的限制或是问题
- 循环开销问题:并发量大的情况下会导致线程一直自旋。
- 只能保证一个变量的原子操作:可以通过AtomicReference来实现对多个变量的原子操作。
- ABA问题:1 -> 3, 3 -> 1。AtomicStampedReference解决(增加版本号)。