[Java源碼][并發(fā)J.U.C]---用代碼一步步實(shí)現(xiàn)ReentrantLock

前言

在前面的文章已經(jīng)介紹了AQS, 接下來的幾篇文章將會(huì)介紹各種鎖, 而且這些鎖都是基于AQS的, 所以需要對AQS有一定的了解將會(huì)幫助我們更容易理解這些鎖. 本文分析的主題是重入鎖ReentrantLock.

本文源代碼 : 源代碼下載

本文會(huì)以三步來進(jìn)行ReentrantLock的分析.

1. 先以一個(gè)小例子來解釋重入鎖的基本概念
2. 用UML畫出該類的結(jié)構(gòu)
3. 分析源碼并且用一個(gè)例子測試

例子1: 簡單理解重入鎖ReentrantLock

重入鎖顧名思義就是說在某一個(gè)線程獲得鎖再次去獲取鎖, 是被允許的, 例如當(dāng)遞歸調(diào)用一個(gè)synchronized修飾的方法, 說明synchronized是可以重入的. 那在[Java源碼][并發(fā)J.U.C]---用代碼一步步實(shí)現(xiàn)AQS(1)---獨(dú)占鎖的獲取和釋放 中的鎖Mutex就不是重入鎖(Mutex類的代碼在代碼下載處可以下載), 因此就用這兩個(gè)鎖比較看一下重入鎖.

public class TestReentrantLock {
    static Lock mutexLock = new Mutex();
    static Lock reentrantLock = new ReentrantLock();
    
    public static void main(String[] args) {
        //new Runner(mutexLock, "thread-1").start();
        new Runner(reentrantLock, "thread-1").start();
    }
    
    static class Runner extends Thread {
        Lock lock;
        public Runner(Lock lock, String name) {
            super(name);
            this.lock = lock;
        }
        public void run() {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " get locks at the first time.");
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " get locks at the second time.");
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + " release locks at the first time.");
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + " release locks at the second time.");
        }
    }
}

當(dāng)使用ReentrantLock的時(shí)候會(huì)輸出如下內(nèi)容. 表明該鎖是可重入的.

thread-1 get locks at the first time.
thread-1 get locks at the second time.
thread-1 release locks at the first time.
thread-1 release locks at the second time.

當(dāng)使用Mutex的時(shí)候會(huì)在輸出第一句話的時(shí)候,就一直阻塞了,表明該線程在第一次獲取鎖成功了,當(dāng)?shù)诙卧偃カ@取鎖的時(shí)候就一直阻塞了.

thread-1 get locks at the first time.

ReentrantLock 內(nèi)部分析

如下是整個(gè)ReentrantLock類所包含的內(nèi)容,關(guān)于Condition的部分我沒有加進(jìn)來,因?yàn)樵诤罄m(xù)的博客中會(huì)有專門的一篇來分析該類.

ReentrantLock.png

如圖所示, 先看右側(cè), ReentrantLock實(shí)現(xiàn)了Lock接口和Serializable接口. 因此就要實(shí)現(xiàn)Lock中的所有方法,也就是在圖中ReentrantLock方法框中. 它的方法主要分三類:
1. 構(gòu)造方法包括ReentrantLock()ReentrantLock(boolean fair)兩個(gè).
2. Lock接口中需要實(shí)現(xiàn)的方法, 從Lock()newCondition()方法.
3. 一些監(jiān)控方法,包括取出等待隊(duì)列中所有在等待的線程等等.從getHoldCount()開始到結(jié)尾.

另外Lock接口需要實(shí)現(xiàn)的方法基本上都是借助Sync類來實(shí)現(xiàn)的,因此ReentrantLock類中有一個(gè)sync的成員變量, 該成員變量可以是Sync的兩個(gè)子類NonfairSync(非公平鎖)和FairSync(公平鎖)中的某一個(gè).

公平性: 再看左側(cè), Sync繼承了AQS,并且實(shí)現(xiàn)了一些共有方法,并且留有抽象方法lock()交由子類各自實(shí)現(xiàn), 子類FairSync表示的是非公平鎖, 意思是如果在絕對時(shí)間上,先對鎖進(jìn)行獲取的請求一定先被滿足,那么這個(gè)鎖是公平的,否則該鎖就是不公平的,也就是NonfairSync類.

其實(shí)ReentrantLock的核心就是Sync及其子類的實(shí)現(xiàn), 因?yàn)閷?shí)現(xiàn)Lock接口中的方法都是通過Sync類的實(shí)例來代理實(shí)現(xiàn)的. 所以接下來就具體看看Sync相關(guān)類的源碼實(shí)現(xiàn).

源碼分析

Sync的實(shí)現(xiàn)
abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        // 留給子類實(shí)現(xiàn)
        abstract void lock();
        
        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()) {  // 重入該鎖 只是累加狀態(tài)
                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(); // 屬于運(yùn)行時(shí)異常
            boolean free = false;
            if (c == 0) {   // 判斷該線程是否完全退出
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);  // 設(shè)置新的狀態(tài)
            return free;
        }
        
        // 判斷當(dāng)前線程是否持有該鎖
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        // condition相關(guān)博客會(huì)分析
        final ConditionObject newCondition() {
            return null;
        }
        // 獲得持有該鎖的線程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        // 獲得持有該鎖的個(gè)數(shù) 其實(shí)就是重入的次數(shù)
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        // 判斷鎖有沒有被占用 true表示被占用 false表示沒有被占用
        final boolean isLocked() {
            return getState() != 0;
        }
        // 序列化的部分
        private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
                s.defaultReadObject();
                setState(0); // reset to unlocked state
        }
    }

從該類的實(shí)現(xiàn)可以看到當(dāng)某一個(gè)線程獲得了鎖后可以無限制的重入, 如果達(dá)到了int變量的最大值后會(huì)拋出Error. 狀態(tài)值為0的時(shí)候表明鎖可以被獲取, 狀態(tài)值為n (n >= 1)的時(shí)候表明該線程重入了n-1次.

非公平鎖NonfairSync的實(shí)現(xiàn)
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        
        // 實(shí)現(xiàn)父類的方法
        final void lock() {
                /**
                 * 如果獲取鎖成功,則設(shè)置當(dāng)前線程
                 * 否則去嘗試獲取鎖
                 */
            if (compareAndSetState(0, 1)) 
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
            
        // 重寫父類的父類AbstractQueuedSynchronizer的tryAcquire方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

其實(shí)沒什么可說的, 需要實(shí)現(xiàn)從父類的lock()抽象方法和重寫AQS中的tryAcquire方法即可.

公平鎖FairSync的實(shí)現(xiàn)
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) { // 檢查等待隊(duì)列前面是不是有線程還沒有獲得鎖
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

與非公平鎖類似實(shí)現(xiàn)locktryAcquire方法, 但是有一點(diǎn)不同的是在嘗試獲取鎖的時(shí)候會(huì)先使用hasQueuedPredecessors判斷等待隊(duì)列中該節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn), 如果有前驅(qū)節(jié)點(diǎn)必須要等到前驅(qū)節(jié)點(diǎn)所對應(yīng)的線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。

Lock接口方法的實(shí)現(xiàn)
    @Override
    public void lock() {
        sync.lock();
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
      // 該方法不存在公平性的問題,所以直接調(diào)用nonfairTryAcquire方法
    @Override
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return null;
    }
一些監(jiān)控方法
// 獲取鎖被重入了多少次
    public int getHoldCount() {
        return sync.getHoldCount();
    }
    
    // 判斷鎖是不是被當(dāng)前線程持有
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    
    // 判斷鎖有沒有被任何一個(gè)線程占有
    public boolean isLocked() {
            return sync.isLocked();
    }
    // 判斷該鎖是不是公平鎖
    public final boolean isFair() {
        return sync instanceof FairSync;
    }
    // 返回占有鎖的那個(gè)線程
    protected Thread getOwner() {
        return sync.getOwner();
    }
    // 返回等待隊(duì)列中是否還有節(jié)點(diǎn) 
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }
    // thread是否在等待隊(duì)列中
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }
    // 返回等待隊(duì)列中的長度
    public final int getQueueLength() {
        return sync.getQueueLength();
    }
    // 返回等待隊(duì)列中的所有線程
    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

注意: getQueuedThreads() 返回等待隊(duì)列中的線程是按倒序的. 具體原因可以參考 [Java源碼][并發(fā)J.U.C]---用代碼一步步實(shí)現(xiàn)AQS(1)---獨(dú)占鎖的獲取和釋放

構(gòu)造方法
    private final Sync sync;
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

默認(rèn)是非公平鎖, 可以通過傳入?yún)?shù)來決定使用非公平鎖還是公平鎖.

例子2: 公平鎖和非公平鎖的對比

啟動(dòng)五個(gè)線程去奪取鎖, 每個(gè)線程的作用就是打印當(dāng)前等待隊(duì)列中所有線程的順序, 打印兩次, 在第一次獲取鎖后打印一次后釋放鎖后再去嘗試獲取鎖.

import java.util.concurrent.CountDownLatch;
public class TestFairAndNonFairLock {
    static ReentrantLock nonfair = new ReentrantLock(false);
    static ReentrantLock fair = new ReentrantLock(true);
    static CountDownLatch start = new CountDownLatch(1);
    // start 是為了保證5個(gè)線程同時(shí)運(yùn)行 后續(xù)有專門博客會(huì)分析CountDownLatch
    public static void main(String[] args) {
        test(fair);
        // test(nonfair);
    }
    
    public static void test (ReentrantLock lock) {
        for (int i = 0; i < 5; i ++) {
            new Thread(new Runner2(lock), i+"").start();
        }
        start.countDown();
    }
    
    static class Runner2 implements Runnable {
        
        ReentrantLock lock;
        public Runner2(ReentrantLock lock) {
            this.lock = lock;
        }
        
        public void run() {
            try {
                start.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            lock.lock();
            System.out.print("lock by " + Thread.currentThread().getName() + " wait by [");
            String str = "";
            for (Thread t : lock.getQueuedThreads()) {
                str = t.getName() + "," + str;
            }
            System.out.println(str + "]");
            lock.unlock();
            
            lock.lock();
            System.out.print("lock by " + Thread.currentThread().getName() + " wait by [");
            str = "";
            for (Thread t : lock.getQueuedThreads()) {
                str = t.getName() + "," + str;
            }
            System.out.println(str + "]");
            lock.unlock();
        }
    }
}

對比如下:

公平鎖 非公平鎖
lock by 3 wait by [4,0,1,2,] lock by 4 wait by [1,3,2,]
lock by 4 wait by [0,1,2,3,] lock by 4 wait by [1,3,2,0,]
lock by 0 wait by [1,2,3,4,] lock by 1 wait by [3,2,0,]
lock by 1 wait by [2,3,4,0,] lock by 1 wait by [3,2,0,]
lock by 2 wait by [3,4,0,1,] lock by 3 wait by [2,0,]
lock by 3 wait by [4,0,1,2,] lock by 3 wait by [2,0,]
lock by 4 wait by [0,1,2,] lock by 2 wait by [0,]
lock by 0 wait by [1,2,] lock by 2 wait by [0,]
lock by 1 wait by [2,] lock by 0 wait by []
lock by 2 wait by [] lock by 0 wait by []

從上圖中可以看到公平鎖在每次釋放鎖后都會(huì)從等待隊(duì)列中的第一個(gè)節(jié)點(diǎn)所對應(yīng)的線程獲得鎖. 而非公平鎖在該線程釋放鎖又基本上再次獲得了鎖, 這是因?yàn)樵卺尫诺倪^程中需要去喚醒等待隊(duì)列中的節(jié)點(diǎn),在喚醒后該線程與喚醒的線程競爭該鎖, 然而從結(jié)果來看剛剛釋放的鎖競爭到鎖的概率比較大.

參考

1. Java并發(fā)編程的藝術(shù)
2. Java1.8 java.util.concurrent.locks包的源代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容