Java 的 atomic 类是一种利用 CAS(Compare And Swap)操作实现的无锁(lock-free)同步机制。现代的 CPU 都支持这种低级别的原子操作。
CAS 包含三个操作数 —— 内存位置(V)、预期的原值(A)和新值(B)。如果内存位置的值与预期值 A 相匹配,那么处理器会自动将该位置的值更新为新值 B,否则处理器不做任何操作。整个比较和交换的操作是一个原子操作。
这种机制有效应用在多线程环境,避免了线程阻塞,并降低了线程上下文切换带来的开销。
在具体实现上,AtomicInteger 类的 incrementAndGet 方法的主要源码如下:
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
// Increment and get value
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
这段代码在使用 incrementAndGet
方法时,会调用 getAndAddInt
方法,这个方法在底层使用 compareAndSwapInt
方法来完成 CAS 操作。compareAndSwapInt
是由操作系统底层实现来提供原子性保证的。
这种机制另一它的优点是避免了死锁。传统的锁同步有可能导致死锁,因为一旦一个线程获取锁后,其他所有需要此锁的线程都会阻塞,如果有互相等待资源的情况,那就可能造成死锁。然而使用 CAS 的话,仅需要在执行操作的时候检查当前状态是否符合预期,并不会造成其他线程长期阻塞,所以没有死锁的问题。
然而,CAS 也并非没有缺点。由于它是无锁的,并发情况下可能会造成 CAS 循环(也称为 busy spin),也就是不断地执行 CAS 操作,直到成功为止。另外,CAS 无法防止 ABA 问题。这是因为如果在此项操作中间有其他线程改变了此数据之后再改回来,这个时候内存值还是会等于预期值,那么 CAS 操作就会执行。为了解决这个问题,Java 提供了一个带有标记的原子引用类 AtomicStampedReference
来解决这个问题。
CAS 机制如何避免死锁
CAS (Compare-And-Swap) 提供了一种有效的避免死锁的方法。在传统的锁同步中,如果两个或多个线程相互等待对方释放资源,就会产生死锁。而 CAS 机制通过无锁的方式来实现线程间的同步,它不需要线程去获取锁,而是直接尝试去更新数据,如果更新失败就继续在循环中尝试,因此不会有死锁问题。
CAS 实际上是一种 "乐观锁",它假设在进行操作时不会有其他线程对数据进行修改,所以尝试去进行操作。如果操作成功,则说明在操作过程中没有其他线程对数据进行修改;如果操作失败,那就说明有其他线程已经修改了数据,这时就需要重新读取数据后再尝试操作。这样,就避免了线程等待锁的问题,也没有产生死锁的可能。