我有一个ConcurrentLinkedQueue
,用作底层数据结构。在每次put调用中,我都会向列表中添加一个唯一的递增值。我有这个方法的同步、比较和交换版本。当我有几个线程(例如,5个)并且总共进行了1000万次放入时,我发现同步版本的效果要好得多。当我有许多线程(例如,2000)并且总共执行相同数量的放入时,我发现CAS工作得更好。为什么CAS与线程较少的阻塞算法相比表现不佳?
// AtomicReference<Foo> latestValue that is initialized
public void put(Double value) {
Foo currentValue;
while (true) {
currentValue = latestValue.get();
Foo newValue = new Foo(value);
if (latestValue.compareAndSet(currentValue, newValue)) {
historyList.add(newValue);
return;
}
}
}
统计
NON-BLOCKING
Threads 2000
Puts per thread 10000
Put time average 208493309
BLOCKING
Threads 2000
Puts per thread 10000
Put time average 2370823534
NON-BLOCKING
Threads 2
Puts per thread 10000000
Put time average 13117487385
BLOCKING
Threads 2
Puts per thread 10000000
Put time average 4201127857
TL;DR,因为在不受控制的情况下,JVM将优化synchronized
并将其替换为CAS锁。
在您的CAS案例中,您有开销:即使CAS失败,您也要尝试进行一些计算。当然,与真正的互斥获取相比,这算不了什么,这通常发生在使用synchronized
时。
但JVM并不愚蠢,当它看到您当前获取的锁是无争用的时,它只是用CAS锁替换真正的互斥锁(甚至在有偏见的锁的情况下用简单的存储)。因此,对于synchronized
的两个线程,您只测量一个CAS,但对于您自己的CAS实现,您还测量分配Foo实例的时间,例如compareAndSet和get()。
对于2000个线程,JVM不执行CAS优化,所以您的实现的性能优于预期的获取互斥对象。