Java分佈式應用學習筆記06淺談並發加鎖機制分析 – JAVA編程語言程序開發技術文章

1.  前言
之前總結的多線程的調度、並發調度、線程加鎖安全等等並發包底層大都使用瞭線程鎖機制。咱們通過鎖的源碼來看看JDK如何將這些資源進行加鎖限制的,怎麼就能做到線程集中等待後就喚醒主線程的。
2.  一段並發包源碼
以下是java.util.concurrent.CyclicBarrier的底層代碼片段。
Java代碼 
private int dowait(boolean timed, long nanos) 
    throws InterruptedException, BrokenBarrierException, 
           TimeoutException { 
    final ReentrantLock lock = this.lock; 
    lock.lock(); 
    try { 
        ……………………省略 
    } finally { 
        lock.unlock(); 
    } 

 
在執行等待的時候,裡面使用瞭ReentrantLock對其進行資源加鎖,保證在代碼塊中使用變量、讀寫變量不會被別的線程打擾。
3.  輕量級鎖ReentrantLock
基於以上程序我們就來看看ReentrantLock內部是如何工作的。ReentrantLock內部有個Sync類,繼承自AbstractQueuedSynchronizer,基於Sync又有2個子類繼承於它,而ReentrantLock就是依靠這2個Sync子類為內核實現的。代碼大傢直接看JDK源程序即可。
ReentrantLock的lock()方法,它的加鎖方法實際上是使用的內部靜態類Sync的方法,至於調用的是公平——FairSync還是不公平的——NonfairSync,這個要看構建ReentrantLock的時候的構造函數瞭。
NonfairSync的加鎖方法實現流程是這樣的:首先基於CAS——Compare and Swap原則,先將state從0嘗試變成1。如果設置成功瞭,證明瞭一個事實——此時此刻,沒有其他的線程持有該鎖。則當前線程設置為ExclusiveOwnerThread(獨傢擁有線程);那麼如果狀態變量state設置不成功呢,則又揭示瞭一個事實,當前線程鎖已經被其他線程所持有,那麼調用acquire方法,該方法首先先嘗試再次獲取狀態state,如果為0瞭,那麼繼續嘗試設置狀態為1。若成功則與此時無其他線程持有鎖操作雷同。如果state依然不為0,則判斷當前線程是否為獨傢擁有線程變量——exclusiveOwnerThread,是的話將state的值+1.如果不是,則將當前線程放入等待隊列中掛起,掛起方法
Java代碼 
public static void park(Object blocker) { 
    Thread t = Thread.currentThread(); 
    setBlocker(t, blocker); 
    unsafe.park(false, 0L); 
    setBlocker(t, null); 

 
FairSync的lock()方法和NonfairSync大同小異。隻不過它沒有獲取state變量信息的過程,直接是調用acquire(1)方法請求線程鎖。
ReentrantLock的unlock()方法,解鎖的方法比較簡單,不區分公平與不公平,都是獲取當前state值,之後減去釋放鎖的個數,減操作後結果如果為0,表示鎖可以釋放瞭。通知隊列上的那些線程,喚醒隊列頭線程,進行run操作。
這些加鎖、解鎖操作很明顯都離不開隊列——AbstractQueuedSynchronizer的輔助操作,將線程組織成為線程隊列形式。
4.  讀寫鎖ReentrantReadWriteLock
瞭解瞭ReentrantLock在加鎖原理上使用瞭一把鎖,一把鑰匙開一把鎖嘛~而如果遇到大部分操作是讀操作的、而寫操作比較少的時候使用ReentrantLock未免有點“奢侈”。使用ReentrantReadWriteLock——讀寫雙鎖進行讀取和寫入,在讀多寫少的場景下可以提升不少性能。它的基本工作原理是這樣的:當使用讀鎖進行lock的時候,就算是有其他線程也進行讀操作,而不是寫操作的時候,線程不會阻塞,可以並行執行,和沒有加lock幾乎是一樣的。當調用寫鎖的lock方法時,無論其他線程是什麼性質的(讀、寫),都阻塞,所以這個雙重鎖適用於讀操作頻率較多、寫操作頻率較少的操作。咱們看個使用例子
Java代碼 
// 讀寫鎖 
static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); 
 
        // 獲得讀鎖-加鎖 
        reentrantReadWriteLock.readLock().lock(); 
        String str = listString.get(sum); 
        reentrantReadWriteLock.readLock().unlock(); 
 
        // 獲得寫鎖-加鎖 
        reentrantReadWriteLock.writeLock().lock(); 
        String str = Thread.currentThread().getName() + "–write:" + sum; 
        listString.add(str); 
        reentrantReadWriteLock.writeLock().unlock(); 
 因為一些特殊原因,不能將源碼完整的場景全部給出,隻能寫出簡單的使用關鍵的片段。
讀鎖的加鎖源代碼片段是
Java代碼 
public final void acquireShared(int arg) { 
    if (tryAcquireShared(arg) < 0) 
        doAcquireShared(arg); 

 
寫鎖的加鎖源碼片段是
Java代碼 
public final void acquire(int arg) { 
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt(); 

 
相比較而言,就是上面說過的一旦寫鎖加鎖時發現有其他線程進行瞭操作,則將當前線程放置於線程等待隊列中——之後再喚醒。而讀操作鎖直接進行瞭共享線程,並發讀取。
5.  總結
綜合前面幾篇線程調度、多線程並發計算等等,底層都是基於加鎖、抽象線程等待隊列AbstractQueuedSynchronizer及其2個具體子類、CAS算法的綜合體現。使用多線程並發包後我們可以構建高可用和高計算能力的分佈式系統。
註意:如果沒有將解鎖代碼寫到finally塊中,是有問題的!!如果發生瞭任何的運行時異常,會向上拋,那麼鎖會永遠不會解除,那麼造成的後果大傢一定知道瞭。

發佈留言