本文共 3981 字,大约阅读时间需要 13 分钟。
假设有个场景,在单实例环境下,记录访问某接口的用户数。为此,设计了一个计数器,代码如下:
public class MyApp { /* volatile 不能保证变量复合操作的原子性 */ private volatile int count = 0; public void updateVistors() { count++; }}
虽然volatile
修饰的共享变量count
的内存可见性能被保证,但count++
操作的原子性则不然,当多线程并发修改count
值时,count
是线程不安全的。
解决这个问题,不少人的第一反应是将count++
的操作加锁(synchronized),如下:
public class MyApp { private int count = 0; /* 带锁的方法 synchronized */ public synchronized void updateVistors() { count++; }}
当然,synchronized
关键字修饰后,count++
操作的原子性和可见性都能被保证,但synchronized
是相对重的锁。只是为了实现小小的自增需求,就引入该锁显得“大材小用”。即使在JDK1.6之后,synchronized
已经因为引入了偏向锁、轻量级锁而被优化很多,但当多线程激烈竞争时,大量线程阻塞、唤醒,相对“无锁并发”实际是大开销的操作。
假如使用线程安全的原子类,由于使用“无锁并发”技术(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.
画成图:
在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
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.AtomicLong
when multiple threadsSo LongAdder is not always a replacement for
consider the following aspects:AtomicLong
. We need to1)When no contention is present
2)AtomicLong
performs better.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.
这段话抓住重点:
LongAdder
吞吐更高,但是消耗内存更大AtomicLong
LongAdder
。CAS的问题有三:
在专题文章 【】中已有说明
CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。
而compareAndSwapInt
就是借助C来调用CPU底层指令实现的。 (具体不再深入) CAS + AQS
是并发包的基石!
转载地址:http://cknsn.baihongyu.com/