CAS
java中CAS (Compare-and-Swap),比较替换,利用交换指令CMPXCHG来实现,能够保证操作的原子性
public final boolean compareAndSet(int expect, int update)
参数 (旧值,新值)
旧值用于校验是否是期待的值,是就能替换成功,不是则失败,可以继续重试来更新。java中自旋锁就是通过循环替换markwork指向来实现的,,线程不会挂起,提高了效率
private int counts = 0;
private AtomicInteger atomicInteger = new AtomicInteger(0);
private void testCAS() {
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
count();
safeCount();
}
}
});
threadList.add(thread);
}
for (Thread thread : threadList) {
thread.start();
}
for (Thread thread : threadList) {
try {
thread.join(); // 挂起测试线程 ,测试线程等所有计数线程执行完毕才会输出结果
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.i("qinxue", "counts: " + counts);
Log.i("qinxue", "atomicInteger: " + atomicInteger.get());
}
private void count() {
counts++;
}
private void safeCount() {
int oldValue = atomicInteger.get(); //原子操作,可看源码 volatile 的。
for (; ; ) {
boolean r = atomicInteger.compareAndSet(oldValue, ++oldValue); //循环尝试更新
if (r) {
break;
}
}
}
结果
counts: 9633822
atomicInteger: 10000000
counts 由于++不是原子性操作,count也没有volatile修饰,各个线程修改后同步到内存,有出现错误。例如2个线程缓存了主存的100,都变成101同步到主存,本来主存应该是102的,现在就出现了错误,正如结果小于10000000
atomicInteger: 1volatile修饰,读取、写入值时是安全的, 2、CAS是原子的,如果在读取到CAS这时间里,别的线程CAS了,本线程的CAS会失败。重试就好了,保证了数据的正确性。
问题
1、ABA问题,例如其他线程修改了1变为2又变回1,本线程CAS没有察觉到起始此值已经变化过了,JDK的AtomicStampedReference解决了这个问题,给值添加了版本号。保证不出现ABA。
2、性能消耗,线程不挂起,无效自旋肯定消耗性能的。
3、只能保证一个变量原子性,不如加锁自由。