Java多線程編程-7 J.U.C之各種鎖

1、什麼是鎖

A lock is a tool for controlling access to a shared resource by
multiple threads. Commonly, a lock provides exclusive access to a
shared resource

引用自JDK的java.util.concurrent.locks.Lock接口說明

鎖即是對共享資源的獨佔訪問的一種機制,當然也有共享資源的鎖,如ReadWriteLock中的Read鎖。

2、聊聊J.U.C中的鎖

JUC中的locks包,包含了多種鎖,如可重入鎖、讀寫鎖及帶戳的版本鎖等等,下面我們看一下這幾個鎖的用途及區別。

2.1 ReentrantLock

ReentrantLock提供了公平和非公平鎖,默認非公平。
分別由FairSyncNonfairSync實現,其父類爲Sync,而Sync父類爲AbstractQueuedSynchronizer,AQS的核心代碼實現了ReentrantLock的鎖的邏輯。

這一章節從源碼角度說明公平和非公平鎖的區別以及AQS是如何實現lock和unlock邏輯的:

java.util.concurrent.locks.ReentrantLock#lock

public void lock() {
	// 調用lock,根據構造的是FairSync還是NonfairSync,調用其lock方法
    sync.lock();
}
// 先看FairSync.lock();
final void lock() {
   // AQS的acquire()
   acquire(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
	// 這裏分爲3步:
	// 第一步:tryAcquire(arg),如果成功獲取到鎖,則lock()方法取鎖完成,否則繼續下一步
	// 第二步:addWaiter(),構造Node並加入到隊尾(AQS包含了head、tail指針,用於記錄等待獲取鎖的線程隊列)
	// 第三步:acquireQueued(),獲取隊列裏的Node,判斷其是否真正獲取到鎖,如果沒有獲取到鎖且需要被打斷則執行selfInterrupt()將當前線程打斷
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // state是AQS中的成員變量,記錄鎖的狀態,大於0說明鎖被某個線程鎖住了
    int c = getState();
    // state == 0, 說明鎖沒有被佔
    if (c == 0) {
    	// 判斷前面是否還要線程再等待(公平鎖就是要等前面先排隊的)
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果當前線程已經加過鎖了,則把state再往上加,這裏就有可重入鎖的味道
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
// 非公平鎖調用的加鎖代碼是其父類Sync的nonfairTryAcquire方法
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    	// 看到鎖沒被其他線程佔住,二話不說直接搶佔
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 這裏和公平鎖一樣的邏輯, state往上加
    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;
}

以上就是ReenTrantLock的加鎖邏輯。

下面分析下unlock釋放鎖:
java.util.concurrent.locks.ReentrantLock#unlock

public void unlock() {
    sync.release(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
	// 先釋放鎖
    if (tryRelease(arg)) {
    	// AQS中的head指針,head不爲空,說明加鎖等待隊列不爲空,需要喚醒head指針中記錄的thread,並將其喚醒
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
// 釋放鎖其實就是修改state的值,如果爲0,說明鎖釋放成功
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;
}

2.2 ReentrantReadWriteLock

我們知道,給共享資源加鎖導致了線程獨佔資源,目的是爲了保護共享資源的正確性。但線程訪問資源,有可能只是去讀取數據,而不會修改數據,這時候其他線程獨佔共享資源,導致讀操作要一直等待,降低了系統性能。

讀寫鎖就是爲了解決這個問題:讀鎖和寫鎖的加鎖關係如下

能否同時加鎖
Y N
N N

只有多個線程都是讀操作,才能共同加“讀”鎖。其他情況下,只有一個線程能加鎖成功。

2.3 StampedLock

JDK1.8及以後版本提供了帶時間戳的鎖,包含了讀寫鎖的功能。這是一個帶版本號的鎖,每次給對象加鎖,會返回一個stamp,之後的釋放鎖需要用到這個stamp,如果和鎖當前的stamp不匹配則釋放鎖失敗。

使用StampedLock
StampedLock源碼分析
StampedLock原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章