當多個線程須要訪問某個公共資源的時候,咱們知道須要經過加鎖來保證資源的訪問不會出問題。java提供了兩種方式來加鎖,一種是關鍵字:synchronized,一種是concurrent包下的lock鎖。synchronized是java底層支持的,而concurrent包則是jdk實現。關於synchronized的原理能夠閱讀再有人問你synchronized是什麼,就把這篇文章發給他。java
在這裏,我會用盡量少的代碼,儘量輕鬆的文字,儘量多的圖來看看lock的原理。安全
咱們以ReentrantLock爲例作分析,其餘原理相似。數據結構
我把這個過程比喻成一個作菜的過程,有什麼菜,作法如何?ide
我先列出lock實現過程當中的幾個關鍵詞:計數值、雙向鏈表、CAS+自旋函數
import java.util.concurrent.locks.ReentrantLock; public class App { public static void main(String[] args) throws Exception { final int[] counter = {0}; ReentrantLock lock = new ReentrantLock(); for (int i= 0; i < 50; i++){ new Thread(new Runnable() { @Override public void run() { lock.lock(); try { int a = counter[0]; counter[0] = a + 1; }finally { lock.unlock(); } } }).start(); } // 主線程休眠,等待結果 Thread.sleep(5000); System.out.println(counter[0]); } }
在這個例子中,開50個線程同時更新counter。分紅三塊來看看源碼(初始化、獲取鎖、釋放鎖)優化
ReentrantLock() 幹了啥ui
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); }
在lock的構造函數中,定義了一個NonFairSync,url
static final class NonfairSync extends Sync
NonfairSync 又是繼承於Syncspa
abstract static class Sync extends AbstractQueuedSynchronizer
一步一步往上找,找到了
這個鬼AbstractQueuedSynchronizer(簡稱AQS),最後這個鬼,又是繼承於AbstractOwnableSynchronizer(AOS),AOS主要是保存獲取當前鎖的線程對象,代碼很少再也不展開。
最後咱們能夠看到幾個主要類的繼承關係。線程
FairSync 與 NonfairSync的區別在於,是否是保證獲取鎖的公平性,由於默認是NonfairSync,咱們以這個爲例瞭解其背後的原理。
其餘幾個類代碼很少,最後的主要代碼都是在AQS中,咱們先看看這個類的主體結構。
AbstractQueuedSynchronizer是個什麼
再看看Node是什麼?
看到這裏的同窗,是否是有種熱淚盈眶的感受,這尼瑪,不就是雙向鏈表麼?我還記得第一次寫這個數據結構的時候,發現竟然還有這麼神奇的一個東西。
最後咱們能夠發現鎖的存儲結構就兩個東西:"雙向鏈表" + "int類型狀態"。
須要注意的是,他們的變量都被"transient
和volatile
修飾。
一個int值,一個雙向鏈表是如何烹飪處理鎖這道菜的呢,Doug Lea大神就是大神,咱們接下來看看,如何獲取鎖?
lock.lock()怎麼獲取鎖?/** * Acquires the lock. */ public void lock() { sync.lock(); }
能夠看到調用的是,NonfairSync.lock()
看到這裏,咱們基本有了一個大概的瞭解,還記得以前AQS中的int類型的state值,這裏就是經過CAS(樂觀鎖)去修改state的值。lock的基本操做仍是經過樂觀鎖來實現的。
獲取鎖經過CAS,那麼沒有獲取到鎖,等待獲取鎖是如何實現的?咱們能夠看一下else分支的邏輯,acquire方法:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
這裏幹了三件事情:
tryAcquire:會嘗試再次經過CAS獲取一次鎖。
addWaiter:將當前線程加入上面鎖的雙向鏈表(等待隊列)中
acquireQueued:經過自旋,判斷當前隊列節點是否能夠獲取鎖。
能夠看到,經過CAS確保可以在線程安全的狀況下,將當前線程加入到鏈表的尾部。
enq是個自旋+上述邏輯,有興趣的能夠翻翻源碼。
能夠看到,噹噹前線程到頭部的時候,嘗試CAS更新鎖狀態,若是更新成功表示該等待線程獲取成功。從頭部移除。
最後簡要歸納一下,獲取鎖的一個流程
public void unlock() { sync.release(1); }
能夠看到調用的是,NonfairSync.release()
最後有調用了NonfairSync.tryRelease()
基本能夠確認,釋放鎖就是對AQS中的狀態值State進行修改。同時更新下一個鏈表中的線程等待節點。
lock的存儲結構:一個int類型狀態值(用於鎖的狀態變動),一個雙向鏈表(用於存儲等待中的線程)
lock獲取鎖的過程:本質上是經過CAS來獲取狀態值修改,若是當場沒獲取到,會將該線程放在線程等待鏈表中。
lock釋放鎖的過程:修改狀態值,調整等待鏈表。
能夠看到在整個實現過程當中,lock大量使用CAS+自旋。所以根據CAS特性,lock建議使用在低鎖衝突的狀況下。目前java1.6之後,官方對synchronized作了大量的鎖優化(偏向鎖、自旋、輕量級鎖)。所以在非必要的狀況下,建議使用synchronized作同步操做。
最後,但願個人分析,能對你理解鎖的實現有所幫助。