天天看點

JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

目錄

  • 前言
  • Reentrantlock結構構成
  • 源碼剖析
    • AQS部分解析
    • Sync源碼
    • NonfairSync
    • FairSync 解析
    • Reentrantlock
  • 結尾

前言

Reentrantlock是可重入的互斥鎖,具有與Synchronized相同的功能,但是卻比Synchronized更加靈活。

Reentrantlock底層基于AbstractQueuedSynchronizer實作,AbstractQueuedSynchronizer抽象類定義了一套多線程通路共享資源的同步模闆,解決了實作同步器時涉及的大量細節問題,隻有少量細節需要自己設定。(AbstractQueuedSynchronizer為加鎖和解鎖過程提供了同步的模闆方法)。

Reentrantlock結構構成

JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

Reentrantlock實作了Lock接口,Reentrantlock具有内部類Sync、NonfairSync、FairSync,其中Sync繼承于AbstractQueuedSynchronizer,Sync實作了釋放資源的細節,NonfairSync是Sync的子類,實作鎖的非公平模式,FairSync也是Sync的子類,實作了鎖的公平模式。

源碼剖析

了解了Reentrantlock的整體結構,接下來我們一個塊一個塊地去分析。

首先我們學習一下Lock接口的源碼

public interface Lock {

    // 擷取 - 不可中斷
    void lock();
    // 擷取 - 可以中斷
    void lockInterruptibly() throws InterruptedException;
    // 擷取鎖 - 可以傳回擷取鎖是否成功
    boolean tryLock();
    // 擷取鎖(可以設定時間) - 可以中斷
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 釋放鎖
    void unlock();
    // 條件變量
    Condition newCondition();
}
/*
這裡的中斷指的是如果目前線程擷取鎖但是未擷取到鎖,我們可以使用中斷方法中斷擷取鎖,而不會使它陷入阻塞狀态。
*/
           

AQS部分解析

AQS中有一個CLH隊列,當擷取鎖失敗時會将線程加入CLH隊列,當一個鎖是非公平鎖時,目前線程擷取鎖時會和隊列的首部線程争搶,成功則設定目前線程為擷取鎖線程,失敗則加入隊列;當一個鎖是公平鎖時,如果隊列為空,則目前線程擷取鎖,如果隊列不為空,則一定是隊列首部線程擷取到鎖,将目前線程加入CLH隊列

JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

Sync源碼

JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

在Reentrantlock中有一個Sync對象,當我們使用的鎖為非公平鎖時,可以利用多态的特性,使其指向NonfairSync,同理,當我們使用的鎖為公平鎖的時候,我們可以将其指向FairSync。又因為Sync繼承了AbstractQueuedSynchronizer,AbstractQueuedSynchronizer提供了加鎖和解鎖過程的模闆方法,是以我們可以看出Sync在Reentrantlock中的重要性。

接下來我們就來看看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();
        // 如果state == 0, 說明現在沒有線程擷取到鎖,則目前線程嘗試加鎖
        if (c == 0) {
            // 嘗試加鎖
            if (compareAndSetState(0, acquires)) {
                // 加鎖成功,設定目前線程為擷取鎖線程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果state != 0, 但是目前線程已經是擷取到鎖的線程,說明目前可以多次擷取鎖(可重入)
        else if (current == getExclusiveOwnerThread()) {
            // 擷取資源,state + 1
            int nextc = c + acquires;
            // 如果state < 0, 抛出異常
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 設定state的狀态,此處不需要CAS,因為目前持有鎖的線程隻有一個,就是自己。
            setState(nextc);
            // 傳回true
            return true;
        }
        // 傳回false
        return false;
    }
	// 釋放資源
    protected final boolean tryRelease(int releases) {
        // state 減去 releases
        int c = getState() - releases;
        // 如果目前線程不是擷取鎖的線程,那肯定無法釋放資源,就抛出異常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        // 預設釋放資源為false
        boolean free = false;
        // 如果state為0,說明已經釋放了全部資源,設定目前持有鎖的線程為null,傳回true
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        // 否則說明沒有釋放全部資源(重入鎖),傳回false
        setState(c);
        return free;
    }
    // 建立條件變量
	final ConditionObject newCondition() {
            return new ConditionObject();
        }
}
           

我們可以發現,Sync實作了非公平擷取鎖的方法,這是給子類NonfairSync用的,但是卻沒有實作公平擷取鎖的方法,需要子類FairSync自己實作。另外Sync還實作了釋放資源的方法。

釋放資源的流程:

  1. 判斷目前線程是否是持有鎖的線程,是就往下執行,不是則抛出異常
  2. 得到state減1的值,之後判斷state是否等于0,等于0設定持有鎖線程為null,傳回true表示釋放資源成功,不等于0就設定state為state-1,傳回false,代表釋放資源失敗(重入鎖)。

這是釋放資源的流程圖

JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

說完了Sync,那麼我們接下來說一下NonfairSync

NonfairSync

NonfairSync是Sync的子類,用于非公平擷取資源

// NonfairSync的源碼
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

	// 加鎖
    final void lock() {
        // 執行CAS操作
        if (compareAndSetState(0, 1))
            // CAS操作成功,設定目前線程為持有鎖線程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 失敗執行AQS擷取鎖模闆流程
            acquire(1);
    }
	// 擷取資源,調用父類Sync實作的非公平擷取資源方法
    protected final boolean tryAcquire(int acquires) {
        // 直接使用父類的非公平擷取資源
        return nonfairTryAcquire(acquires);
    }
}
// AQS中的acquire(int arg)方法
public final void acquire(int arg) {
    	// 再執行一次擷取資源方法,成功就結束,失敗就将目前線程加入CLH隊列
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
/*
我們看一下父類Sync實作非公平擷取資源的細節
	// 非公平擷取資源
    final boolean nonfairTryAcquire(int acquires) {
        // 擷取目前線程
        final Thread current = Thread.currentThread();
        // 擷取目前狀态
        int c = getState();
        // 如果state == 0, 說明現在沒有線程擷取到鎖,則目前線程嘗試加鎖
        if (c == 0) {
            // 嘗試加鎖
            if (compareAndSetState(0, acquires)) {
                // 加鎖成功,設定目前線程為擷取鎖線程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果state != 0, 但是目前線程已經是擷取到鎖的線程,說明目前可以多次擷取鎖(可重入)
        else if (current == getExclusiveOwnerThread()) {
            // 擷取資源,state + 1
            int nextc = c + acquires;
            // 如果state < 0, 抛出異常
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 設定state的狀态,此處不需要CAS,因為目前持有鎖的線程隻有一個,就是自己。
            setState(nextc);
            // 傳回true
            return true;
        }
        // 傳回false
        return false;
    }
*/
           

我們說一下非公平擷取資源的方式:

  1. 判斷state是否等于0,等于0就嘗試加鎖,加鎖成功就設定目前線程為持有鎖的線程,傳回true,否則往下執行
  2. 判斷目前線程和擷取鎖的線程是不是同一個線程,是同一個線程,就讓state+1,傳回true,否則傳回false
JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

FairSync 解析

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
	// 執行AQS擷取鎖模闆函數
    final void lock() {
        acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // state為0,之後判斷目前線程是不是隊列被喚醒的線程
        if (c == 0) {
            // 是隊列被喚醒的線程,嘗試加鎖
            // hasQueuedPredecessors()傳回false說明目前線程是隊列的頭部線程,就嘗試擷取鎖
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                // 加鎖成功就設定目前線程為持有鎖的線程,傳回true
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 目前線程和持有鎖的線程是同一個線程,就讓state+1,設定state,傳回true,否則傳回false
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 設定state
            setState(nextc);
            return true;
        }
        return false;
    }
}
           

FairSync是公平擷取鎖,擷取資源流程和NonfairSync擷取資源的流程很相似,隻是加了一個判斷,判斷如果目前線程不在隊列就加入隊列,如果目前線程在隊列就判斷目前線程的前面還有沒有線程。

JUC之Reentrantlock源碼解析前言Reentrantlock結構構成源碼剖析結尾

Reentrantlock

最後我們說一下Reentrantlock的源碼

// Reentrantlock的部分源碼
public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    // Sync對象
    private final Sync sync;
    // 預設為非公平鎖
    public ReentrantLock() {
            sync = new NonfairSync();
        }
    // 設定鎖為公平鎖還是非公平鎖
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    // 擷取鎖
    public void lock() {
            sync.lock();
        }
    // 擷取鎖 - 可以中斷
    public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }
    // 擷取鎖 - 傳回擷取鎖成功還是失敗
    public boolean tryLock() {
            return sync.nonfairTryAcquire(1);
        }
    // 擷取鎖 - 可以設定時間,可以中斷
    public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
    // 釋放鎖
    public void unlock() {
            // 使用Sync的release方法
            sync.release(1);
        }
    // 條件變量
    public Condition newCondition() {
            // 使用Sync的建立條件變量方法
            return sync.newCondition();
        }    

}
           

Reentrantlock有兩個構造方法,無參的構造方法為非公平鎖,有參的構造方法可以設定公平鎖,Reentrantlock鎖的實作重點靠裡面的Sync對象,Sync繼承了AQS,又有兩個子類,使用Sync對象可以很好的實作功能。

結尾

最後我們說一下與Synchronized相比Reentrantlock有以下幾個特性

  • 可重入
  • 可中斷
  • 可以設定逾時時間
  • 可以設定為公平鎖
  • 鎖的粒度更小,功能更靈活

相信學習了Reentrantlock的結構和内部源碼,我們對于它的使用肯定可以更加得心應手。

學習于:

程式猿阿星

米蘭的小鐵匠z