文章目录
本文介绍了CAS算法的应用场景及原理。
1、volatile不能保证原子性
假设有个场景,在单实例环境下,记录访问某接口的用户数。为此,设计了一个计数器,代码如下:
public class MyApp {
/* volatile 不能保证变量复合操作的原子性 */
private volatile int count = 0;
public void updateVistors() {
count++;
}
}
虽然volatile
修饰的共享变量count
的内存可见性能被保证,但count++
操作的原子性则不然,当多线程并发修改count
值时,count
是线程不安全的。
2、synchronized是大开销操作
解决这个问题,不少人的第一反应是将count++
的操作加锁(synchronized),如下:
public class MyApp {
private int count = 0;
/* 带锁的方法 synchronized */
public synchronized void updateVistors() {
count++;
}
}
当然,synchronized
关键字修饰后,count++
操作的原子性和可见性都能被保证,但synchronized
是相对重的锁。只是为了实现小小的自增需求,就引入该锁显得“大材小用”。即使在JDK1.6之后,synchronized
已经因为引入了偏向锁、轻量级锁而被优化很多,但当多线程激烈竞争时,大量线程阻塞、唤醒,相对“无锁并发”实际是大开销的操作。
3、CAS(原子类)解决方案
假如使用线程安全的原子类,由于使用“无锁并发”技术(CAS),开销相对synchronized
要小很多。
public class MyApp1 {
private final AtomicLong al = new AtomicLong(0);
public void updateVisitors() {
al.incrementAndGet();
}
}
相对于synchronized
这样的pessimistic locking
(悲观锁),CAS是一种更轻量的optimistic locking
(乐观锁)技术。
CAS(Compare and Swap)技术的核心思想如下:
This algorithm compares the contents of a memory location to a given
value and, only if they are the same, modifies the contents of that
memory location to a given new value.This is done as a single atomic operation. The atomicity
guarantees that the new value is calculated based on up-to-date
information; if the value had been updated by another thread in the
meantime, the write would fail.The result of the operation must indicate whether it performed the
substitution; this can be done either with a simple Boolean response
(this variant is often called compare-and-set), or by returning
the value read from the memory location (not the value written to it).
- A memory location V where value has to be replaced
- Old value A which was read by thread last time
- New value B which should be written over V
CAS says “I think V should have the value A; if it does, put B there,
otherwise don’t change it but tell me I was wrong.” CAS is an
optimistic technique—it proceeds with the update in the hope of
success, and can detect failure if another thread has updated the
variable since it was last examined.
画成图:
4、原子类的核心源码分析
在JDK8之前,代码如是写道:
public final long incrementAndGet() {
for (;;) {
long old = get(); // the value read by thread last time
long next = old + 1; // new value to be written over V
if (compareAndSet(current, next))
return next;
}
}
JDK8之后,采用了更能说明其本质的写法:
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
这样写有什么好处呢?
unsafe.getAndAddLong()
会被JIT优化。在x86架构下,这句代码只对应到一句CPU指令LOCK XADD
,这个指令比普通的循环CAS的性能更好。
在多线程激烈竞争的场景下,synchronized
可能比原子变量性能更好(多线程空自旋会影响性能?),不过多数情况下(in realistic contention levels )原子变量比synchronized
性能更好。若有疑问请参考
Java8还引入了LongAdder
类,其表述如下:
This class is usually preferable to
AtomicLong
when multiple threads
update a common sum that is used for purposes such as collecting
statistics, not for fine-grained synchronization control. Under low
update contention, the two classes have similar characteristics. But
under high contention, expected throughput of this class is
significantly higher, at the expense of higher space consumption.So LongAdder is not always a replacement for
AtomicLong
. We need to
consider the following aspects:1)When no contention is present
AtomicLong
performs better.
2)LongAdder
will allocateCells
(a final class declared in
abstract classStriped64
) to avoid contention which consumes memory.
So in case we have a tight memory budget we should prefer AtomicLong.
这段话抓住重点:
- 1)激烈冲突下,
LongAdder
吞吐更高,但是消耗内存更大 - 2)低冲突下,使用
AtomicLong
- 3)在类似数据统计,而不是精确同步的场景下,更推荐
LongAdder
。
5、CAS的问题
CAS的问题有三:
- 1)ABA问题
- 2)多线程长期自旋忙等锁,造成CPU开销大
- 3)CAS只能对单个变量实现原子操作
在专题文章 【Java如何实现原子操作】中已有说明
6、更深入:CAS的原理
CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。
而compareAndSwapInt
就是借助C来调用CPU底层指令实现的。
(具体不再深入)
7、CAS在并发包的地位
CAS + AQS
是并发包的基石!
8、CAS思想在业务代码中的应用
参考文献
- https://dzone.com/articles/how-cas-compare-and-swap-java