1. 程式人生 > >JDK併發AQS系列(三)

JDK併發AQS系列(三)

鎖的獲取與釋放

在資料競爭情況下,一個執行緒只有在成功獲取鎖後才能繼續往下執行,當離開競爭區域時將釋放鎖,釋放的鎖供其他即將進入資料競爭區域的執行緒獲取。

同步器一般用acquire和release方法執行獲取釋放鎖操作,acquire方法包括的邏輯是先嚐試獲取鎖,成功則往下執行,否則把執行緒放到等待佇列中並可能將執行緒阻塞;release方法包含的邏輯是釋放鎖,喚醒等待佇列中一個或多個執行緒去嘗試獲取鎖。看看在AQS中鎖的獲取與釋放。

獲取鎖邏輯

if(嘗試獲取鎖失敗) {
    建立node
    使用CAS方式把node插入到佇列尾部
    while(true){
        if
(嘗試獲取鎖成功 並且 node的前驅節點為頭節點){ 把當前節點設定為頭節點 跳出迴圈 }else{ 使用CAS方式修改node前驅節點的waitStatus標識為signal if(修改成功) 掛起當前執行緒 } } } 複製程式碼

釋放鎖邏輯

if(嘗試釋放鎖成功){
    喚醒後續節點包含的執行緒
}
複製程式碼

自旋鎖

所謂自旋鎖即是某一執行緒去嘗試獲取某個鎖時,如果該鎖已經被其他執行緒佔用的話,此執行緒將不斷迴圈檢查該鎖是否被釋放,而不是讓此執行緒掛起或睡眠。它屬於為了保證共享資源而提出的一種鎖機制,與互斥鎖類似,保證了公共資源在任意時刻最多隻能由一條執行緒獲取使用,不同的是互斥鎖在獲取鎖失敗後將進入睡眠或阻塞狀態。下面利用程式碼實現一個簡單的自旋鎖,

public class SpinLock {
	private static Unsafe unsafe = null;
	private static final long valueOffset;
	private volatile int value = 0;
	static {
		try {
			unsafe=getUnsafeInstance();
			valueOffset = unsafe.objectFieldOffset(SpinLock.class
					.getDeclaredField("value"));
		} catch (Exception ex) {
			throw new Error(ex);
		}
	}
	private static Unsafe getUnsafeInstance() throws SecurityException,
			NoSuchFieldException, IllegalArgumentException,
			IllegalAccessException {
		Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe"
); theUnsafeInstance.setAccessible(true); return (Unsafe) theUnsafeInstance.get(Unsafe.class); } public void lock() { for (;;) { int newV = value + 1; if(newV==1) if (unsafe.compareAndSwapInt(this, valueOffset, 0, newV)) return ; } } public void unlock() { unsafe.compareAndSwapInt(this, valueOffset, 1, 0); } } 複製程式碼

這是一個很簡單的自旋鎖,主要看lock和unlock兩個方法,Unsafe僅僅是為操作提供了硬體級別的原子CAS操作,暫時忽略此類,只要知道它的作用即可,我們將在後面的“原子性如何保證”小節中對此進行更加深入的闡述。

對於lock方法,假如有若干執行緒競爭,能成功通過CAS操作修改value值為newV的執行緒即是成功獲取鎖的執行緒,將直接通過,而其他的執行緒則不斷在迴圈檢測value值是否又改回0,將value改為0的操作就是獲取鎖的執行緒執行完後對該鎖進行釋放,通過unlock方法釋放鎖,釋放後若干執行緒又對該鎖競爭。如此一來,沒獲取的鎖也不會被掛起或阻塞,而是不斷迴圈檢查狀態。

根據下圖可加深自旋鎖的理解,五條執行緒輪詢value變數,t1獲取成功後將value置為1,此狀態時其他執行緒無法競爭鎖,t1使用完鎖後將value置為0,剩下的執行緒繼續競爭鎖,以此類推。這樣就保證了某個區域塊的執行緒安全性。

image

總結

自旋鎖適用於鎖佔用時間短,即鎖保護臨界區很小的情景,同時它需要硬體級別操作,也要保證各快取資料的一致性,另外,無法保證公平性,不保證先到先獲得,可能造成執行緒飢餓。在多處理器機器上,每個執行緒對應的處理器都對同一個變數進行讀寫,而每次讀寫操作都將要同步每個處理器快取,導致系統性能嚴重下降。

-------------推薦閱讀------------

我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)

為什麼寫《Tomcat核心設計剖析》

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

歡迎關注: