CAS 也叫自旋锁,用来保证操作的一致性,比如用2个线程同时对一个变量累加1000,000次,得到的结果可能比2000,000少:
package juc.test.cas;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
public class NonAtomicTest {
static long var = 0 ;
final int totalThread = 2;
final long timesEveryThread = 1000000;
@Test
public void testAtomicLong(){
CountDownLatch cdl = new CountDownLatch(totalThread); // thread count
for (int j = 0; j < totalThread; j++) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < timesEveryThread; i++) {
var = var + 1;
}
cdl.countDown();
}
}.start();
}
try {
cdl.await(); // wait for all thread return
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(var);
}
}
输出:
1259956
其中的原因有可能是因为他们读取的值一样,计算的结果也一样,而我们希望的结果是一个线程的操作是基于上次计算的结果,CAS可以保证,它的执行流程如图:
值得一提的是,“比较(Compare)”和“交换(Swap)” 这两个操作需要合并为一个原子操作并加锁,也就是当有一个线程在进行cmpxchg时其他线程需要等待,防止出现2个线程同时compare发现都ok,然后都替换,比如两个线程都对i加1,i初始化为0,它们同时读取到i为0,并计算得到结果1,并且同时进行compare发现都是前值和现值都是0,很match,然后俩线程都写入1;加上一把锁,即使这俩线程同时读取到i为0,进行compare时必须有先后顺序,就可以保证操作的一致性了。
Java的原子类AtomicInteger等是利用CAS算法实现的,例程的CAS版:
package juc.test.cas;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
// See also ./hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp:93
public class AtomicTest {
final int totalThread = 2;
final long timesEveryThread = 1000000;
@Test
public void testAtomicLong(){
AtomicLong var = new AtomicLong() ;
CountDownLatch cdl = new CountDownLatch(totalThread);
for (int j = 0; j < totalThread; j++) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < timesEveryThread; i++) {
var.getAndIncrement();
}
cdl.countDown();
}
}.start();
}
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(var.get());
}
}
输出:
2000000