學(xué)習(xí)非阻塞的同步機(jī)制CAS
在研究線程池的執(zhí)行原理時(shí),看到一段不斷循環(huán)重試的代碼,不理解它的原理,看注釋這是CAS的實(shí)現(xiàn),所以學(xué)會(huì)之后記錄下來(lái)。
鎖有什么劣勢(shì)
在多線程并發(fā)下,可以通過(guò)加鎖來(lái)保證線程安全性,但多個(gè)線程同時(shí)請(qǐng)求鎖,很多情況下避免不了要借助操作系統(tǒng),線程掛起和恢復(fù)會(huì)存在很大的開(kāi)銷,并存在很長(zhǎng)時(shí)間的中斷。一些細(xì)粒度的操作,例如同步容器,操作往往只有很少代碼量,如果存在鎖并且線程激烈地競(jìng)爭(zhēng),調(diào)度的代價(jià)很大。
總結(jié)來(lái)說(shuō),線程持有鎖,會(huì)讓其他需要鎖的線程阻塞,產(chǎn)生多種風(fēng)險(xiǎn)和開(kāi)銷。加鎖是一種悲觀方法,線程總是設(shè)想在自己持有資源的同時(shí),肯定有其他線程想要資源,不牢牢鎖住資源還不能放心呢。
在硬件的支持下,出現(xiàn)了非阻塞的同步機(jī)制,其中一種常用實(shí)現(xiàn)就是CAS。
什么是CAS
現(xiàn)代的處理器都包含對(duì)并發(fā)的支持,其中最通用的方法就是比較并交換(compare and swap),簡(jiǎn)稱CAS。
CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。無(wú)論V值是否等于A值,都將返回V的原值。CAS 有效地說(shuō)明了:我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。
當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新一個(gè)變量,最終只有一個(gè)線程會(huì)成功,其他線程都會(huì)失敗。但和使用鎖不同,失敗的線程不會(huì)被阻塞,而是被告之本次更新操作失敗了,可以再試一次。此時(shí),線程可以根據(jù)實(shí)際情況,繼續(xù)重試或者跳過(guò)操作,大大減少因?yàn)樽枞鴵p失的性能。所以,CAS是一種樂(lè)觀的操作,它希望每次都能成功地執(zhí)行更新操作。
public class SimulationCAS { private int value; public synchronized int get() { return value; } public synchronized boolean compareAndSet(int expectedValue, int newValue) { if (expectedValue == compareAndSwap(expectedValue, newValue)) { return true; } return false; } public synchronized int compareAndSwap(int expectedValue, int newValue) { int oldValue = value; if (oldValue == expectedValue) { value = newValue; } return oldValue; } }
上面的代碼模擬了CAS的操作,其中compareAndSwap是CAS語(yǔ)義的體現(xiàn),compareAndSet對(duì)value進(jìn)行了更新操作,并返回成功與否。
幾行代碼就實(shí)現(xiàn)了CAS,是不是覺(jué)得很簡(jiǎn)單呢?但你要知道,CAS僅僅告訴你操作結(jié)果,操作失敗后一系列重試回退放棄等操作都要自己實(shí)現(xiàn),開(kāi)發(fā)起來(lái)遠(yuǎn)比使用鎖復(fù)雜。
Atom原子類
JVM是支持CAS的,體現(xiàn)在我們常用的Atom原子類,拿AtomicInteger分析一下源碼。
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
對(duì)AtomicInteger進(jìn)行+1操作,循環(huán)里,會(huì)將當(dāng)前值和+1后的目標(biāo)值傳入compareAndSet,直到成功才跳出方法。compareAndSet是不是很熟悉呢,接著來(lái)看看它的代碼。
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
compareAndSet調(diào)用了unsafe.compareAndSwapInt,這是一個(gè)native方法,原理就是調(diào)用硬件支持的CAS方法??炊@個(gè)應(yīng)該就能明白Atom類的原理,其他方法的實(shí)現(xiàn)是類似的。
線程池里的CAS
有了CAS的基礎(chǔ)后,可以來(lái)研究那段我未看懂的代碼。
提交一個(gè)執(zhí)行任務(wù),線程池會(huì)嘗試增加一個(gè)工作線程去處理任務(wù)。下面是ThreadPoolExecutor里addWorker的一段代碼:
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } //其他省略
在內(nèi)循環(huán)里,會(huì)調(diào)用compareAndIncrementWorkerCount方法增加一個(gè)工作線程,原理和AtomicInteger的getAndIncrement方法是一樣的。如果增加成功,直接跳出循環(huán),否則在檢查線程池狀態(tài)后,再次在內(nèi)循環(huán)調(diào)用compareAndIncrementWorkerCount,直到添加成功。
現(xiàn)在再看代碼,瞬間就明白了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring自動(dòng)配置之condition條件判斷下篇
這篇文章主要為大家介紹了SpringBoot?condition條件判斷功能的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08改善Java代碼之慎用java動(dòng)態(tài)編譯
這篇文章主要介紹了改善Java代碼之慎用java動(dòng)態(tài)編譯,需要的朋友可以參考下2021-04-04Java concurrency之集合_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java集合主體內(nèi)容包括Collection集合和Map類;而Collection集合又可以劃分為L(zhǎng)ist(隊(duì)列)和Set(集合),有需要的小伙伴可以參考下2017-06-06Mapper層繼承BaseMapper<T>需要引入的pom依賴方式
這篇文章主要介紹了Mapper層繼承BaseMapper<T>需要引入的pom依賴方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01使用Java實(shí)現(xiàn)簡(jiǎn)單搭建內(nèi)網(wǎng)穿透
內(nèi)網(wǎng)穿透是一種網(wǎng)絡(luò)技術(shù),適用于需要遠(yuǎn)程訪問(wèn)本地部署服務(wù)的場(chǎng)景,本文主要為大家介紹了如何使用Java實(shí)現(xiàn)簡(jiǎn)單搭建內(nèi)網(wǎng)穿透,感興趣的可以了解下2024-02-02