摘要:算法算法會先對一個內(nèi)存變量位置和一個給定的值進行比較,如果相等,則用一個新值去修改這個內(nèi)存變量位置。因為是非公平鎖,所以一上來就嘗試搶占鎖給定舊值并希望用新值去更新內(nèi)存變量。
本文翻譯和原創(chuàng)各占一半,所以還是厚顏無恥歸類到原創(chuàng)好了...
https://howtodoinjava.com/jav...
java 5 其中一個令人振奮的改進是新增了支持原子操作的類型,例如 AtomicInteger, AtomicLong 等。在多線程環(huán)境中進行簡單的自增自減操作時,這些原子類能幫助你減少很多用于多線程同步的復雜代碼。這些原子類依賴于 CAS (compare and swap) 算法,接下來我們會討論 CAS 這個概念。
傳統(tǒng)的鎖機制,例如 java 的 synchronized 關鍵字,他代表了 java 中悲觀鎖技術(shù),保證了某一時刻僅有一個線程能訪問同步代碼/方法。synchronized 能夠很好地工作,卻有著 (相對) 比較大的性能開銷。
樂觀鎖 (相對悲觀鎖) 對性能會有很大的幫助。他的核心思想是:你寄希望于在沒有沖突的情況下完成一次更新操作,使用樂觀鎖技術(shù)更新時會進行 “沖突檢測” 來判斷是否有其他的線程干擾,若是 (有其他線程干擾) 則視本次更新操作失敗,一般會進行重試 (可以了解一下CAS自旋)。Compare and Swap 就是典型的樂觀鎖技術(shù)。
CAS 算法會先對一個內(nèi)存變量(位置) V 和一個給定的值進行比較 A ,如果相等,則用一個新值 B 去修改這個內(nèi)存變量(位置)。上述過程會作為一個原子操作完成 (intel處理器通過 cmpxchg 指令系列實現(xiàn))。CAS 原子性保證了新值的計算是基于上一個有效值,期間如果內(nèi)存變量(位置) V 被其他線程更新了,本線程的 CAS 更新操作將會失敗。CAS 操作必須告訴調(diào)用者成功與否,可以返回一個 boolean 值來表示,或者返回一個從內(nèi)存變量讀到的值 (應該是上一次有效值)
CAS 操作數(shù)有三個:
內(nèi)存變量(位置) V,表示被更新的變量
線程上一次讀到的舊值 A
用來覆蓋 V 的新值 B
CAS 表示:“我認為現(xiàn)在 V 的值還是之前我讀到的舊值 A,若是則用新值 B 覆蓋內(nèi)存變量 V,否則不做任何動作并告訴調(diào)用者操作失敗”。CAS 是一項樂觀鎖技術(shù),他在更新的時候總是希望能成功 (沒有沖突),但也能檢測出來自其他線程的沖突和干擾Java 中的 Compare and Swap
這里我們關注一下ReentrantLock鎖定和解鎖那部分的源碼
//ReentrantLock.lock() public void lock() { sync.lock(); }
他依賴了其內(nèi)部類Sync的 lock(),以下是內(nèi)部類 Sync (繼承了隊列同步器 AQS)
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; abstract void lock(); ................
Sync還是個抽象類,一般 new ReentrantLock() 時創(chuàng)建的是 NonfairSync
// ReentrantLock的構(gòu)造方法 public ReentrantLock() { sync = new NonfairSync(); }
下面就是NonfairSync的 lock() 方法了
final void lock() { if (compareAndSetState(0, 1)) // 1 setExclusiveOwnerThread(Thread.currentThread()); // 2 else acquire(1); // 3 }
1 中的 compareAndSetState() 承繼自隊列同步器 AQS,封裝了 CAS 指令。因為是 NonfairSync 非公平鎖,所以一上來就嘗試搶占鎖:給定舊值 0 并希望用新值 1 去更新內(nèi)存變量 State。若更新成功則視為獲取鎖成功,并執(zhí)行 2
2 成功完成了 CAS 操作 (沒錯,當你使用 CAS 指令成功把 State 從 0 更新成 1 便視為獲取鎖,就是這么簡單粗暴 ╮(╯▽╰)╭ ),把當前線程設為獨占線程
3 操作失敗 (被人搶先獲取鎖(╯`□′)╯╧╧),進行 acquire 操作再次嘗試獲取鎖,若還是不行,則把當前線程加入 AQS 等待隊列,由 AQS 來管理隊列中等待線程的阻塞和喚醒,具體代碼就不貼出來了,AQS 的源碼多處使用到 CAS 指令,有興趣的同學可以查看
鎖用完了要釋放,下面貼出 unlock() 方法
// ReentrantLock.unlock() public void unlock() { sync.release(1); }
這里還是依賴了 sync,release() 是 AQS 的通用方法,其內(nèi)部調(diào)用了 tryRelease() (由 Sync 類實現(xiàn)),這里直接貼出 Sync 的 tryRelease()
protected final boolean tryRelease(int releases) { // releases 參數(shù)的值是上面?zhèn)鬟M來的 1 int c = getState() - releases; // 1 if (Thread.currentThread() != getExclusiveOwnerThread()) // 1.5 throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 2 free = true; setExclusiveOwnerThread(null); } setState(c); // 3 return free; }
1 處的c 是內(nèi)存變量 State 即將要被更新的值,因為 ReentrantLock 是可重入鎖 (當前線程可多次獲取鎖),所以 State 的值是可以大于 1 的。
2 判斷若新值為 0,則視為鎖被釋放并設置當前獨占線程為 null
3 把 State 的值更新為 c,思考一下這里的更新操作為什么沒用到 CAS 指令?
1.5 解釋了上面的疑問,只有當前獨占線程有能力對 State 變量進行修改,不需要進行同步或使用 CAS
SummaryAQS 隊列同步器以及 java.util.concurrent 下各種鎖和原子類都運用到的 CAS 算法,有時間的同學建議閱讀加深印象。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/73081.html
摘要:否則它就會用新的值替代當前值。在這種情況下,鎖可能會優(yōu)于原子變量,但在實際的爭用級別中,原子變量的性能優(yōu)于鎖。在中引入了另外一個構(gòu)件。 題目要求 在我們深入了解CAS(Compare And Swap)策略以及它是如何在AtomicInteger這樣的原子構(gòu)造器中使用的,首先來看一下這段代碼: public class MyApp { private volatile int ...
閱讀 1716·2021-09-08 10:42
閱讀 3672·2021-08-11 10:23
閱讀 4181·2019-08-30 14:10
閱讀 2795·2019-08-29 17:29
閱讀 3154·2019-08-29 12:50
閱讀 695·2019-08-26 13:36
閱讀 3522·2019-08-26 11:59
閱讀 1549·2019-08-23 16:23