天天看點

【JUC】公平鎖,非公平鎖源碼分析Lock

其中有一些值的概念不太清楚,參考了:

https://blog.csdn.net/lsgqjh/article/details/63685058(這一位大佬,講的很細!!)

https://blog.csdn.net/mulinsen77/article/details/84583716

在此感謝!

Lock

Lock接口功能:

public interface Lock {
    // 獲得鎖
	void lock();
    // 獲得鎖
    void unlock();
    // lock非阻塞版本,成功傳回true
    boolean tryLock();
    // 添加嘗試時間,時間到傳回false
    boolean tryLock(long time, TimeUnit unit)
    // 傳回一個螢幕對象
    Condition newCondition();
}
           

AQS

在看ReenTrantLock之前,先大緻看看AQS都做了什麼:

這是一個簡化的線程隊列模型:

【JUC】公平鎖,非公平鎖源碼分析Lock

重要屬性:

  • state:代表了資源是否處于鎖定狀态;

    1:鎖定(已經有線程拿鎖,如果重入了,此值一直累加)2:未鎖定

    線程拿鎖,就是通過CAS修改state,修改成功,則拿到鎖;

  • Node内部類:

    每一個Node裝載一個線程;對線程通過雙向連結清單的方式排隊;

  • Node内部類:還定義資源是 獨占 / 還是共享

    也就是每個線程都有一個mode,辨別是獨占,還是共享;

    Node EXCLUSIVE:代表獨占;

    Node SHARED:代表共享;

先看幾個重要方法,後面會用到;

acquire

acquire

:顧名思義擷取,擷取鎖的方法

tryAcquire

:就先當作是獲得鎖,傳回true,沒拿到鎖,傳回false;這個方法是ReenTrantLock的方法,後面會講;

addWaiter

:如果沒拿到鎖,将目前要拿鎖的線程加入線程隊列

acquireQueued

:已經入隊完成,會進一步判斷,後面講;主要是判斷線程是否被挂起;

  • true:挂起
  • false:拿鎖成功了

是以:

當:拿鎖失敗,并且線程被挂起,就會執行

selfInterrupt();

,執行線程的中斷;

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
           

addWaiter

這個方法就是:(CAS操作入隊)

讓目前線程包裝成Node,

隊列存在,直接入隊

如果隊列不存在,調用

enq(node);

,初始化隊列,再入隊

(入隊:将node設定為隊列的tail尾部節點,并且與tail雙向關聯起來)

private Node addWaiter(Node mode) {
    // 包裝線程為Node,并且是獨占的
    Node node = new Node(Thread.currentThread(), mode);
    // 拿到線程隊列的尾節點
    Node pred = tail;
    // 如果pred存在,即隊列非空
    if (pred != null) {
        node.prev = pred;
        // CAS操作成功入隊,将Node設定為tail
        if (compareAndSetTail(pred, node)) {
            // 因為是雙向連結清單,要再鍊一次
            pred.next = node;
            return node;
        }
    }
    // 隊列為空,調用enq,初始化隊列,并入隊
    enq(node);
    return node;
}
           

acquireQueued

此方法:是線程沒拿到鎖,入隊之後執行;

主要是:如果發現目前線程的前一個結點是隊列的head,那麼會再次嘗試拿鎖;

也就是說,此時線程隊列,就倆線程,一個是head,持鎖線程,一個是目前線程;

那麼目前線程會不斷嘗試拿鎖(

for (;;)

最終傳回的boolean型

interrupted

  • true:挂起
  • false:拿鎖成功了
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 拿到目前線程的前一任節點
            final Node p = node.predecessor();
            // 發現前任是head,再次嘗試拿鎖
            if (p == head && tryAcquire(arg)) {
                // 拿鎖成功,node設定為head
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 判斷是否将目前線程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
           

ReenTrantLock

實作Lock接口

ReenTrantLock隻有一個内部屬性:就是

Sync内部類

的鎖抽象對象

// 這是一個父類,兩個子類分别實作公平鎖,非公平鎖
private final Sync sync;
           

三個内部類:

  • Sync(繼承AQS):鎖抽象;
  • NonfairSync(繼承Sync):非公平鎖抽象;
  • FairSync(繼承Sync):公平鎖抽象;

構造器

我們在建立ReenTrantLock對象,調用構造器時,就會建立不同的Sync(鎖抽象的實作)

// 非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
// 公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
           

公平鎖源碼(拿鎖,排隊,重入鎖)

當我們調用了

lock.lock();

公平鎖下,sync已經是

FairSync

的執行個體了;

調用

sync.lock()

public void lock() {
    sync.lock();
}
           

然後調用FairSync内部類下的lock方法:(建議點進源碼,看下)

acquire(1)

:此方法是AQS下的方法;(去上面看!)在内部是調用了下面的tryAcquire方法;

這個參數1是幹嘛的:就代表嘗試擷取鎖;之前AQS的屬性state,如果為0表示未鎖定;

這個1就是要通過

compareAndSetState(0, acquires)

CAS操作進行加鎖的;

(這裡也是通過記憶體位址stateOffset,拿到state的狀态,CAS操作不再贅述)

嘗試将state設定為1,即拿到鎖;

重點:tryAcquire方法(實作了拿鎖,排隊,重入鎖)

static final class FairSync extends Sync {
    final void lock() {
        acquire(1); // 調用AQS acquire方法,前面講了
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 從AQS中拿到目前資源的state狀态
        int c = getState();
        // 如果為0,則表示未鎖定,可以嘗試擷取鎖
        if (c == 0) {
            // hasQueuedPredecessors是看目前線程隊列中是否有其他線程(非公平鎖沒有此判斷)
            // 如果有其他線程,目前線程不允許拿鎖,而是去排隊
            // 如果沒有線程,并且CAS操作将state置1,那麼目前線程就拿到了鎖
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                // 設定獨占的資源持有者為目前線程,即拿鎖,并傳回true
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // state非0,即資源已被鎖定
        // 判斷目前的線程,是不是占用鎖的線程
        // 是,則累加state,也就是重入鎖的實作
        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;
    }
}
           

非公平鎖源碼

同樣是lock()方法,不再贅述,隻不過這裡的

Sync

執行個體,是

NonfairSync

的執行個體;

與公平鎖的主要差別:線程是否排隊

直接看NonfairSync

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
	// 加鎖方法
    final void lock() {
        // CAS嘗試加鎖
        if (compareAndSetState(0, 1))
            // 成功,設定資源獨占者為目前線程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 底層依然調用下面的tryAcquire
            acquire(1);
    }

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

nonfairTryAcquire

方法是其父類

Sync

下的方法

類似于公平鎖的

tryAcquire

方法

差別是:不再進行

hasQueuedPredecessors()

方法的判斷,直接嘗試擷取鎖

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 這裡是差別,不再判斷是否隊列中是否有其他線程,也就是不需要排隊,直接嘗試擷取鎖
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 重入邏輯
    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;
}
           
JUC

繼續閱讀