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,剩下的執行緒繼續競爭鎖,以此類推。這樣就保證了某個區域塊的執行緒安全性。
