无锁并发的CAS为何如此优秀?
Talk is cheap
CAS(Compare And Swap),即比較并交換。是解決多線(xiàn)程并行情況下使用鎖造成性能損耗的一種機(jī)制,CAS操作包含三個(gè)操作數(shù)——內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。無(wú)論位置V的值是否等于A, 都將返回V原有的值。
CAS的含義是”我認(rèn)為V的值應(yīng)該是A,如果是,那我將V的值更新為B,否則不修改并告訴V的值實(shí)際是多少“
Show you my code
在單線(xiàn)程環(huán)境中分別使用無(wú)鎖,加鎖以及cas進(jìn)行十組5億次累加運(yùn)算,然后打印出平均耗時(shí)。
/*** cas對(duì)比加鎖測(cè)試** @author Jann Lee* @date 2019-11-21 0:12**/ public class CasTest {@Testpublic void test() {long times = 500_000_000;// 記錄耗時(shí)List<Long> elapsedTime4NoLock = new ArrayList<>(10);List<Long> elapsedTime4Synchronized = new ArrayList<>(10);List<Long> elapsedTime4ReentrantLock = new ArrayList<>(10);List<Long> elapsedTime4Cas = new ArrayList<>(10);// 進(jìn)行10組試驗(yàn)for (int j = 0; j < 10; j++) {// 無(wú)鎖long startTime = System.currentTimeMillis();for (long i = 0; i < times; i++) {}long endTime = System.currentTimeMillis();elapsedTime4NoLock.add(endTime - startTime);// synchronized 關(guān)鍵字(隱式鎖)startTime = endTime;for (long i = 0; i < times; ) {i = addWithSynchronized(i);}endTime = System.currentTimeMillis();elapsedTime4Synchronized.add(endTime - startTime);// ReentrantLock 顯式鎖startTime = endTime;ReentrantLock lock = new ReentrantLock();for (long i = 0; i < times; ) {i = addWithReentrantLock(i, lock);}endTime = System.currentTimeMillis();elapsedTime4ReentrantLock.add(endTime - startTime);// cas(AtomicLong底層是用cas實(shí)現(xiàn))startTime = endTime;AtomicLong atomicLong = new AtomicLong();while (atomicLong.getAndIncrement() < times) {}endTime = System.currentTimeMillis();elapsedTime4Cas.add(endTime - startTime);}System.out.println("無(wú)鎖計(jì)算耗時(shí): " + average(elapsedTime4NoLock) + "ms");System.out.println("synchronized計(jì)算耗時(shí): " + average(elapsedTime4Synchronized) + "ms");System.out.println("ReentrantLock計(jì)算耗時(shí): " + average(elapsedTime4ReentrantLock) + "ms");System.out.println("cas計(jì)算耗時(shí): " + average(elapsedTime4Cas) + "ms");}/*** synchronized加鎖*/private synchronized long addWithSynchronized(long i) {i = i + 1;return i;}/*** ReentrantLock加鎖*/private long addWithReentrantLock(long i, Lock lock) {lock.lock();i = i + 1;lock.unlock();return i;}/*** 計(jì)算平均耗時(shí)*/private double average(Collection<Long> collection) {return collection.stream().mapToLong(i -> i).average().orElse(0);} }從案例中我們可能看出在單線(xiàn)程環(huán)境場(chǎng)景下cas的性能要高于鎖相關(guān)的操作。當(dāng)然,在競(jìng)爭(zhēng)比較激烈的情況下性能可能會(huì)有所下降,因?yàn)?strong>要不斷的重試和回退或者放棄操作,這也是CAS的一個(gè)缺點(diǎn)所在,因?yàn)檫@些重試,回退等操作通常用開(kāi)發(fā)者來(lái)實(shí)現(xiàn)。
CAS的實(shí)現(xiàn)并非是簡(jiǎn)單的代碼層面控制的,而是需要硬件的支持,因此在不同的體系架構(gòu)之間執(zhí)行的性能差異很大。但是一個(gè)很管用的經(jīng)驗(yàn)法則是:在大多數(shù)處理器上,在無(wú)競(jìng)爭(zhēng)的鎖獲取和釋放的”快速代碼路徑“上的開(kāi)銷(xiāo),大約是CAS開(kāi)銷(xiāo)的兩倍。
為何CAS如此優(yōu)秀
硬件加持,現(xiàn)代大多數(shù)處理器都從硬件層面通過(guò)一些列指令實(shí)現(xiàn)CompareAndSwap(比較并交換)同步原語(yǔ),進(jìn)而使操作系統(tǒng)和JVM可以直接使用這些指令實(shí)現(xiàn)鎖和并發(fā)的數(shù)據(jù)結(jié)構(gòu)。我們可以簡(jiǎn)單認(rèn)為,CAS是將比較和交換合成是一個(gè)原子操作。
JVM對(duì)CAS的支持, 由于Java程序運(yùn)行在JVM上,所以應(yīng)對(duì)不同的硬件體系架構(gòu)的處理則需要JVM來(lái)實(shí)現(xiàn)。在不支持CAS操作的硬件上,jvm將使用自旋鎖來(lái)實(shí)現(xiàn)。
CAS的ABA問(wèn)題
cas操作讓我們減少了鎖帶來(lái)的性能損耗,同時(shí)也給我們帶來(lái)了新的麻煩-ABA問(wèn)題。
在線(xiàn)程A讀取到x的值與執(zhí)行CAS操作期間,線(xiàn)程B對(duì)x執(zhí)行了兩次修改,x的值從100變成200,然后再?gòu)?00變回100;而后在線(xiàn)程A執(zhí)行CAS操作過(guò)程中并未發(fā)現(xiàn)x發(fā)生過(guò)變化,成功修改了x的值。由于x的值100 ->200->100,所以稱(chēng)之為ABA的原因。
魔高一尺道高一丈,解決ABA的問(wèn)題目前最常用的辦法就是給數(shù)據(jù)加上“版本號(hào)”,每次修改數(shù)據(jù)時(shí)同時(shí)改變版本號(hào)即可。
Q&A
在競(jìng)爭(zhēng)比較激烈的情況下,CAS要進(jìn)行回退,重試等操作才能得到正確的結(jié)果,那么CAS一定比加鎖性能要高嗎?
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的无锁并发的CAS为何如此优秀?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 八点建议写出优雅的 Java 代码
- 下一篇: NYOJ 298 点的变换(矩阵快速幂)