【JUC】JDK1.8源碼分析之ReentrantLock(三)


一、前言

  在分析了AbstractQueuedSynchronier源碼后,接着分析ReentrantLock源碼,其實在AbstractQueuedSynchronizer的分析中,已經提到過ReentrantLock,ReentrantLock表示下面具體分析ReentrantLock源碼。

二、ReentrantLock數據結構

  ReentrantLock的底層是借助AbstractQueuedSynchronizer實現,所以其數據結構依附於AbstractQueuedSynchronizer的數據結構,關於AQS的數據結構,在前一篇已經介紹過,不再累贅。

三、ReentrantLock源碼分析

  3.1 類的繼承關系 

public class ReentrantLock implements Lock, java.io.Serializable

  說明:ReentrantLock實現了Lock接口,Lock接口中定義了lock與unlock相關操作,並且還存在newCondition方法,表示生成一個條件。

  3.2 類的內部類

  ReentrantLock總共有三個內部類,並且三個內部類是緊密相關的,下面先看三個類的關系。

  說明:ReentrantLock類內部總共存在Sync、NonfairSync、FairSync三個類,NonfairSync與FairSync類繼承自Sync類,Sync類繼承自AbstractQueuedSynchronizer抽象類。下面逐個進行分析。

  1. Sync類

  Sync類的源碼如下  

abstract static class Sync extends AbstractQueuedSynchronizer {
        // 序列號
        private static final long serialVersionUID = -5179523762034025860L;
        
        // 獲取鎖
        abstract void lock();
        
        // 非公平方式獲取
        final boolean nonfairTryAcquire(int acquires) {
            // 當前線程
            final Thread current = Thread.currentThread();
            // 獲取狀態
            int c = getState();
            if (c == 0) { // 表示沒有線程正在競爭該鎖
                if (compareAndSetState(0, acquires)) { // 比較並設置狀態成功,狀態0表示鎖沒有被占用
                    // 設置當前線程獨占
                    setExclusiveOwnerThread(current); 
                    return true; // 成功
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 當前線程擁有該鎖
                int nextc = c + acquires; // 增加重入次數
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                // 設置狀態
                setState(nextc); 
                // 成功
                return true; 
            }
            // 失敗
            return false;
        }
        
        // 試圖在共享模式下獲取對象狀態,此方法應該查詢是否允許它在共享模式下獲取對象狀態,如果允許,則獲取它
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread()) // 當前線程不為獨占線程
                throw new IllegalMonitorStateException(); // 拋出異常
            // 釋放標識
            boolean free = false; 
            if (c == 0) {
                free = true;
                // 已經釋放,清空獨占
                setExclusiveOwnerThread(null); 
            }
            // 設置標識
            setState(c); 
            return free; 
        }
        
        // 判斷資源是否被當前線程占有
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 新生一個條件
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class
        // 返回資源的占用線程
        final Thread getOwner() {        
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        // 返回狀態
        final int getHoldCount() {            
            return isHeldExclusively() ? getState() : 0;
        }

        // 資源是否被占用
        final boolean isLocked() {        
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        // 自定義反序列化邏輯
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
View Code

  說明:Sync類存在如下方法和作用如下。

  2. NonfairSync類

  NonfairSync類繼承了Sync類,表示采用非公平策略獲取鎖,其實現了Sync類中抽象的lock方法,源碼如下。

// 非公平鎖
    static final class NonfairSync extends Sync {
        // 版本號
        private static final long serialVersionUID = 7316153563782823691L;

        // 獲得鎖
        final void lock() {
            if (compareAndSetState(0, 1)) // 比較並設置狀態成功,狀態0表示鎖沒有被占用
                // 把當前線程設置獨占了鎖
                setExclusiveOwnerThread(Thread.currentThread());
            else // 鎖已經被占用,或者set失敗
                // 以獨占模式獲取對象,忽略中斷
                acquire(1); 
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
View Code

  說明:從lock方法的源碼可知,每一次都嘗試獲取鎖,而並不會按照公平等待的原則進行等待,讓等待時間最久的線程獲得鎖。

  3. FairSyn類

  FairSync類也繼承了Sync類,表示采用公平策略獲取鎖,其實現了Sync類中的抽象lock方法,源碼如下。 

// 公平鎖
    static final class FairSync extends Sync {
        // 版本序列化
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 以獨占模式獲取對象,忽略中斷
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        // 嘗試公平獲取鎖
        protected final boolean tryAcquire(int acquires) {
            // 獲取當前線程
            final Thread current = Thread.currentThread();
            // 獲取狀態
            int c = getState();
            if (c == 0) { // 狀態為0
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) { // 不存在已經等待更久的線程並且比較並且設置狀態成功
                    // 設置當前線程獨占
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 狀態不為0,即資源已經被線程占據
                // 下一個狀態
                int nextc = c + acquires;
                if (nextc < 0) // 超過了int的表示范圍
                    throw new Error("Maximum lock count exceeded");
                // 設置狀態
                setState(nextc);
                return true;
            }
            return false;
        }
    }
View Code

  說明:跟蹤lock方法的源碼可知,當資源空閑時,它總是會先判斷sync隊列(AbstractQueuedSynchronizer中的數據結構)是否有等待時間更長的線程,如果存在,則將該線程加入到等待隊列的尾部,實現了公平獲取原則。其中,FairSync類的lock的方法調用如下,只給出了主要的方法。

  說明:可以看出只要資源被其他線程占用,該線程就會添加到sync queue中的尾部,而不會先嘗試獲取資源。這也是和Nonfair最大的區別,Nonfair每一次都會嘗試去獲取資源,如果此時該資源恰好被釋放,則會被當前線程獲取,這就造成了不公平的現象,當獲取不成功,再加入隊列尾部。

  3.3 類的屬性

public class ReentrantLock implements Lock, java.io.Serializable {
    // 序列號
    private static final long serialVersionUID = 7373984872572414699L;    
    // 同步隊列
    private final Sync sync;
}

  說明:ReentrantLock類的sync非常重要,對ReentrantLock類的操作大部分都直接轉化為對Sync和AbstractQueuedSynchronizer類的操作。

  3.4 類的構造函數

  1. ReentrantLock()型構造函數  

public ReentrantLock() {
        // 默認非公平策略
        sync = new NonfairSync();
    }
View Code

  說明:可以看到默認是采用的非公平策略獲取鎖。

  2. ReentrantLock(boolean)型構造函數

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
View Code

  說明:可以傳遞參數確定采用公平策略或者是非公平策略,參數為true表示公平策略,否則,采用非公平策略。

  3.5 核心函數分析

  通過分析ReentrantLock的源碼,可知對其操作都轉化為對Sync對象的操作,由於Sync繼承了AQS,所以基本上都可以轉化為對AQS的操作。如將ReentrantLock的lock函數轉化為對Sync的lock函數的調用,而具體會根據采用的策略(如公平策略或者非公平策略)的不同而調用到Sync的不同子類。

  所以可知,在ReentrantLock的背后,是AQS對其服務提供了支持,由於之前我們分析AQS的核心源碼,遂不再累贅。下面還是通過例子來更進一步分析源碼。

四、示例分析

  4.1 公平鎖 

package com.hust.grid.leesf.abstractqueuedsynchronizer;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyThread extends Thread {
    private Lock lock;
    public MyThread(String name, Lock lock) {
        super(name);
        this.lock = lock;
    }
    
    public void run () {
        lock.lock();
        try {
            System.out.println(Thread.currentThread() + " running");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            lock.unlock();
        }
    }
}

public class AbstractQueuedSynchonizerDemo {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock(true);
        
        MyThread t1 = new MyThread("t1", lock);        
        MyThread t2 = new MyThread("t2", lock);
        MyThread t3 = new MyThread("t3", lock);
        t1.start();
        t2.start();    
        t3.start();
    }
}

  運行結果(某一次): 

Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running

  說明:該示例使用的是公平策略,由結果可知,可能會存在如下一種時序。

  說明:首先,t1線程的lock操作 -> t2線程的lock操作 -> t3線程的lock操作 -> t1線程的unlock操作 -> t2線程的unlock操作 -> t3線程的unlock操作。根據這個時序圖來進一步分析源碼的工作流程。

  ① t1線程執行lock.lock,下圖給出了方法調用中的主要方法。

  說明:由調用流程可知,t1線程成功獲取了資源,可以繼續執行。

  ② t2線程執行lock.lock,下圖給出了方法調用中的主要方法。

  

  說明:由上圖可知,最后的結果是t2線程會被禁止,因為調用了LockSupport.park。

  ③ t3線程執行lock.lock,下圖給出了方法調用中的主要方法。

  說明:由上圖可知,最后的結果是t3線程會被禁止,因為調用了LockSupport.park。

  ④ t1線程調用了lock.unlock,下圖給出了方法調用中的主要方法。

  說明:如上圖所示,最后,head的狀態會變為0,t2線程會被unpark,即t2線程可以繼續運行。此時t3線程還是被禁止。

  ⑤ t2獲得cpu資源,繼續運行,由於t2之前被park了,現在需要恢復之前的狀態,下圖給出了方法調用中的主要方法。

  說明:在setHead函數中會將head設置為之前head的下一個結點,並且將pre域與thread域都設置為null,在acquireQueued返回之前,sync queue就只有兩個結點了。

  ⑥ t2執行lock.unlock,下圖給出了方法調用中的主要方法。

  說明:由上圖可知,最終unpark t3線程,讓t3線程可以繼續運行。

  ⑦ t3線程獲取cpu資源,恢復之前的狀態,繼續運行。

  說明:最終達到的狀態是sync queue中只剩下了一個結點,並且該節點除了狀態為0外,其余均為null。

  ⑧ t3執行lock.unlock,下圖給出了方法調用中的主要方法。

  說明:最后的狀態和之前的狀態是一樣的,隊列中有一個空節點,頭結點為尾節點均指向它。

  使用公平策略和Condition的情況可以參考上一篇關於AQS的源碼示例分析部分,不再累贅。

五、總結

  再掌握了AQS后,再來分析ReentrantLock的源碼,就會非常簡單,因為ReentrantLock的絕大部分操作都是基於AQS類的。所以,進行分析時要找准方向,就會事半功倍。謝謝各位園友觀看~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM