java中在Unsafe中提供了三种CAS操作:
compareAndSwapInt(),compareAndSwapObject(),compareAndSwapLong()
//参数含义:对象,属性的内存偏移量,属性期望值,属性更新值
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
偏移量:内存中的对象包含对象头和对象实例数据,而对象头占8个字节,对于64位操作系统的压缩指针占4个字节, 所以我们一般说对象头占12个字节;例如对于一个test对象,x偏移量为这个对象的对象头即12个字节,y的偏移量为16
cas操作修改Test类的x变量。
public class CASTest {
public static void main(String[] args) {
Test test = new Test();
Unsafe unsafe = UnsafeFactory.getUnsafe();
long xOffset = UnsafeFactory.getFieldOffset(unsafe, Test.class, "x");
System.out.println(xOffset); //12
long yOffset = UnsafeFactory.getFieldOffset(unsafe, Test.class, "y");
System.out.println(yOffset); //16
unsafe.compareAndSwapInt(test, xOffset, 0, 1);
System.out.println(test.x);
}
static class Test {
int x;
int y;
}
}
CAS可以保证原子性,但是不能保证有序性和可见性,所以一般地,CAS配合volatile使用可以保证线程安全。底层最终通过执行一个cas指令(原子操作修改变量值),通过期望值和内存中的实际值进行比较,比较的结果如果相等则返回的是旧值(期望值)表示CAS操作成功,不相等则返回内存中实际值表示CAS操作失败。
CAS实现线程安全的操作
public class CASTest {
private static int sum = 0;
private static CASLock casLock = new CASLock();
public static void main(String[] args) throws InterruptedException {
for (int i=0; i<10; i++) {
new Thread(() -> {
for (;;) {
if (casLock.getState() == 0 && casLock.cas()) {
try {
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
casLock.setState(0);
}
break;
}
}
}).start();
}
Thread.sleep(2000);
System.out.println(sum);
}
}
public class CASLock {
private volatile int state = 0;
private static final Unsafe UNSAFE;
private static final long OFFSET;
static {
UNSAFE = UnsafeFactory.getUnsafe();
OFFSET = UnsafeFactory.getFieldOffset(UNSAFE, CASLock.class, "state");
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public boolean cas() {
return UNSAFE.compareAndSwapInt(this, OFFSET, 0, 1);
}
}
jdk中juc包下原子类都是通过cas实现线程安全的。
ABA的问题可以通过版本号来解决
LongAdder、DoubleAdder原理
在高并发下,CAS操作会有大量的线程自旋,导致浪费线程资源,而为了提高执行效率,将V值拆分为多个变量,多个线程同时对各自的变量进行cas操作,所有线程执行完成后,将所有变量进行累加统计。其思想与jdk8中ConcurrentHashMap统计元素个数类似,LongAdder、DoubleAdder也实现了该思想。LongAdder中定义了base变量和cell数组变量,通过hash对Cell数组初始化和累加操作,最后将base和Cell数组所有的数进行累加得到结果。