CAS是JDK提供的非阻塞原子操作,它通过硬件保证了比较-更新的原子性。它是非阻塞的且自身具有原子性,也就是说CAS效率高、可靠。CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe类提供的CAS方法(如:compareAndSwapXXX)底层实现就是CPU的cmpxchg指令。
执行cmpxchg指令指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间很短,所以多线程情况下性能会比较好。
CAS,compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用的一种技术。它包含三个操作数:内存位置、预期原值、更新值。执行CAS操作的时候,将内存位置的值与预期原值进行比较:
- 如果匹配,那么处理器会自动将该位置的更新为新值。
- 如果不匹配,处理器不做任何操作或者重试,当它重试这种行为我们称之为:自旋。多个线程同时执行CAS操作只会有一个线程成功。
没有CAS之前
有个Resource资源类,为了保证addNumber()方法中的number++的原子性,我们必须使用synchronized将该方法上锁。
class Resource {
//保证可见性
volatile Integer number = 0;
public Integer getNumber() {
return number;
}
//加锁保证原子性
public synchronized void addNumber() {
number++;
}
}
使用CAS
如果使用AtomicInteger原子整型类,我们就不需要synchronized这样重量级的锁来保证多线程环境下的并发问题导致的问题。
class Resource {
private AtomicInteger atomicInteger = new AtomicInteger();
public Integer getAtomicInteger() {
return atomicInteger.get();
}
public void setAtomicInteger() {
atomicInteger.getAndIncrement();
}
}
使用100个线程,每个线程对atomicInteger 进行加100次。逻辑上我们的最终打印的结果应该是:10000。
//线程数
private static final Integer THREAD_COUNTS = 100;
private static final CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNTS);
public static void main(String[] args) {
Resource resource = new Resource();
for (int i = 0; i < THREAD_COUNTS; i++) {
new Thread(() -> {
//每个线程加100次
for (int j = 0; j < 100; j++) {
resource.setAtomicInteger();
}
countDownLatch.countDown();
}, "线程:" + i).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("得到最终值:" + resource.getAtomicInteger());
}
打印结果:
得到最终值:10000