主要的误解是假设您正在比较“CAS
vs. synchronized
”。鉴于复杂的 JVM 如何实现synchronized
,您正在比较一个的性能CAS
基于算法使用AtomicLong
与性能CAS
用于实现的基于算法synchronized
.
如同Lock
,对象监视器的内部信息基本上由int
状态表明它是否已被拥有以及嵌套的频率,对当前所有者线程的引用以及等待能够获取它的线程队列。昂贵的方面是等待队列。将线程放入队列、将其从线程调度中删除以及最终在当前所有者释放监视器时将其唤醒,这些操作可能会花费大量时间。
然而,在无竞争的情况下,当然不涉及等待队列。采集监视器由单个CAS
将状态从“无主”(通常为零)更改为“拥有,获得一次”(猜测典型值)。如果成功,线程可以继续执行关键操作,然后释放,这意味着仅写入具有必要内存可见性的“无主”状态,并唤醒另一个被阻塞的线程(如果有)。
由于等待队列的成本要高得多,因此即使在竞争情况下,实现通常也会尝试通过执行一定量的旋转来避免它,从而使多次重复CAS
在回退到使线程排队之前尝试。如果所有者的关键操作就像单个乘法一样简单,那么监视器在旋转阶段就已经被释放的可能性很高。注意synchronized
这是“不公平的”,允许旋转线程立即继续,即使已经有排队的线程等待更长时间。
如果您比较执行的基本操作synchronized(lock){ n = n * 123; }
当不涉及排队时al.updateAndGet(x -> x * 123);
,你会发现它们大致相当。主要区别在于AtomicLong
方法将重复争用乘法,而对于synchronized
方法中,如果在旋转期间没有取得进展,则存在被放入队列的风险。
But synchronized
allows 锁粗化 https://shipilev.net/jvm-anatomy-park/1-lock-coarsening-for-loops/对于在同一对象上重复同步的代码,这可能与调用syncShared
方法。除非还有一种方法可以融合多个CAS
的更新AtomicLong
,这可以给synchronized
一个巨大的优势。 (也可以看看本文 https://www.ibm.com/developerworks/library/j-jtp10185/index.html涵盖了上面讨论的几个方面)
请注意,由于“不公平”性质synchronized
,创建比 CPU 核心多得多的线程不一定是问题。在最好的情况下,“线程数减去核心数”线程最终会出现在队列中,永远不会醒来,而其余线程在旋转阶段成功,每个核心上有一个线程。但同样,不在 CPU 核心上运行的线程也不会降低 CPU 的性能。AtomicLong
更新,因为它们既不能使当前值对其他线程无效,也不能使失败CAS
试图。
无论哪种情况,当CAS
在非共享对象的成员变量上或执行时synchronized
在非共享对象上,JVM 可以检测操作的本地性质并消除大部分相关成本。但这可能取决于几个微妙的环境因素。
最重要的是,在原子更新和synchronized
块。通过更昂贵的操作,事情会变得更加有趣,这可能会增加线程在竞争情况下排队的可能性synchronized
,这使得在原子更新的竞争情况下必须重复该操作是可以接受的。