天天看點

【Java8源碼分析】locks包-ReentrantLock

轉載請注明出處:http://blog.csdn.net/linxdcn/article/details/72850375

1 輔助内部類

在ReetrantLock内部基于AQS類實作了一個抽象類Sync同步器,對于AQS不熟悉的,可以看看這篇文章:AbstractQueuedSynchronizer同步器。基于Sync還實作了公平鎖和非公平鎖:

  • 公平鎖
    • 線程按照他們送出請求的順序擷取鎖
    • 如果有另一個線程持有鎖或者有其他線程在等待隊列中等待這個所,那麼新發出的請求的線程将被放入到隊列中。
  • 非公平鎖
    • 當一個線程請求非公平鎖時,如果在送出請求的同時該鎖變成可用狀态,那麼這個線程可能會跳過隊列中所有的等待線程而獲得鎖
    • 非公平鎖上,隻有當鎖被某個線程持有時,新送出請求的線程才會被放入隊列中
//抽象同步器Sync繼承AQS,子類可以非公平或者公平鎖
abstract static class Sync extends AbstractQueuedSynchronizer {

    abstract void lock();

    // 非公平鎖的嘗試擷取
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == ) {
            // 跟公平鎖擷取的差別時,這裡少了判斷隊列是否為空的函數hasQueuedPredecessors
            // 即不管排隊隊列是否為空,該線程都将直接嘗試擷取鎖
            if (compareAndSetState(, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 以下這個函數實作了重入鎖
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < ) // 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 == ) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
}

//非公平鎖的同步器
static final class NonfairSync extends Sync {

    final void lock() {
        if (compareAndSetState(, ))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire();
    }

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

// 公平鎖的同步器
static final class FairSync extends Sync {

    final void lock() {
        acquire();
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == ) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < )
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}
           

2 主要方法

ReentrantLock的方法源碼比較簡單,主要是委托給Sync同步器實作。

//構造函數,預設使用非公平鎖同步器
public ReentrantLock() {
    sync = new NonfairSync();
}

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

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

public void unlock() {
    sync.release();
}

// 實作了條件變量
public Condition newCondition() {
    return sync.newCondition();
}
           

3 條件變量

條件變量是實作了Java中的Condition接口的類,其中Condition中的await、signal、signalAll方法與Object中的wait、notify、notifyAll類似。

假設現在我們有三個線程A、B、C,申請了一個ReetrantLock,以及兩個條件變量,如下代碼,下面舉例說明Condition如何工作

Lock lock = new ReentrantLock();
Condition conditionX = lock.newCondition();
Condition conditionY = lock.newCondition();
           

初始狀态

假設已經有一個線程擷取的了lock,然後線程A、B、C依次擷取lock,狀态圖如下:

【Java8源碼分析】locks包-ReentrantLock

Step 1

線程A、B分别調用了

conditionX.await()

,源碼如下

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 把等待線程包裝為Node
    Node node = addConditionWaiter();
    // 釋放鎖
    int savedState = fullyRelease(node);
    int interruptMode = ;
    while (!isOnSyncQueue(node)) {
        // 線程在此挂起
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != )
            break;
    }

    // 當收到signal通知後,線程會繼續,搶占鎖
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 如果搶占不成功,線程繼續挂起
    if (interruptMode != )
        reportInterruptAfterWait(interruptMode);
}

//在等待隊列中建立一個Node
private Node addConditionWaiter() {
    Node t = lastWaiter;
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
           

線程A、B均在函數

LockSupport.park(this)

處被挂起,等待通知,目前的狀态圖如下:

【Java8源碼分析】locks包-ReentrantLock

Step 2

線程C獲得鎖,執行。當C執行完後,調用

signal

通知

await

的線程

// 将一個等待時間最長的waitQueue的Node移到lockQueue
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {

    if (!compareAndSetWaitStatus(node, Node.CONDITION, ))
        return false;

    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws >  || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
           
【Java8源碼分析】locks包-ReentrantLock

4 總結

(1)synchronized關鍵字無法中斷一個正在等候獲得鎖的線程,也無法通過輪詢得到鎖;ReentrantLock 類實作了 Lock ,它擁有與 synchronized 相同的并發性和記憶體語義,但是添加了類似輪詢鎖、定時鎖等候和可中斷鎖等候的一些特性。

(2)根類 Object 包含某些特殊的方法,用來線上程的 wait() 、 notify() 和 notifyAll() 之間進行通信;Lock 架構包含了對 wait 和 notify 的概括,這個概括叫作 條件(Condition),對于指定的 Lock ,可以有不止一個條件變量與它關聯。

(3)synchronized的優點:使用 synchronized 的時候,不可能忘記釋放鎖;在退出 synchronized 塊時,JVM 會做這件事;當 JVM 用 synchronized 管理鎖定請求和釋放時,JVM 在生成線程轉儲時能夠包括鎖定資訊。這些對調試非常有價值,因為它們能辨別死鎖或者其他異常行為的來源

(4)java.util.concurrent.lock 中的鎖定類是用于進階使用者和進階情況的工具 。一般來說,除非您對 Lock 的某個進階特性有明确的需要,或者有明确的證據(而不是僅僅是懷疑)表明在特定情況下,同步已經成為可伸縮性的瓶頸,否則還是應當繼續使用 synchronized。

參考

https://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html

轉載請注明出處:http://blog.csdn.net/linxdcn/article/details/72850375