ReentrantLock與ReentrantReadWriteLock源碼分析

《Java并發(fā)編程之美》讀書(shū)筆記

獨(dú)占鎖ReentrantLock的原理

類(lèi)圖結(jié)構(gòu)

ReentrantLock是可重入的獨(dú)占鎖茅郎,同時(shí)只能有一個(gè)線程可以獲取到該鎖,其他獲取該鎖的線程會(huì)被阻塞返給到AQS阻塞隊(duì)里面涝缝。


Xnip2019-08-22_12-34-24.jpg

從類(lèi)圖看到窑眯,ReentrantLock最終還是基于AQS來(lái)實(shí)現(xiàn)的梭伐,并且能夠根據(jù)參數(shù)來(lái)決定其內(nèi)部是一個(gè)公平鎖還是非公平鎖,默認(rèn)是非公平鎖。

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

其中Sync類(lèi)直接繼承自AQS蚀苛,它的子類(lèi)NonfairSync和FairSync分別實(shí)現(xiàn)了獲取鎖的非公平策略玩郊。
在這里,AQS的狀態(tài)值state表示該線程獲取鎖的可重入次數(shù),在默認(rèn)情況下枉阵,state的指表示當(dāng)前鎖沒(méi)有被任何線程持有译红,當(dāng)一個(gè)線程第一次獲取該鎖時(shí)會(huì)嘗試使用CAS設(shè)置state的狀態(tài)值為1,如果CAS成功則當(dāng)前線程獲取了該鎖兴溜,單后記錄該鎖的持有者為當(dāng)前線程侦厚。在該線程沒(méi)有釋放鎖的情況下第二次獲取該鎖后,狀態(tài)值被設(shè)置為2拙徽,這就是可重入次數(shù)刨沦,在該線程釋放該所時(shí),會(huì)嘗試使用CAS讓狀態(tài)值為1膘怕,如果減1后狀態(tài)值為0想诅,則當(dāng)前線程釋放該鎖。

獲取鎖

1.void lock()方法
當(dāng)一個(gè)線程調(diào)用該方法時(shí)候岛心,說(shuō)明該線程希望獲取該鎖来破,如果鎖當(dāng)前沒(méi)有被其他線程占用并且當(dāng)前線程之前沒(méi)有獲取過(guò)該鎖,則當(dāng)前線程會(huì)獲取該鎖忘古,然后設(shè)置當(dāng)前鎖的擁有者為當(dāng)前線程徘禁,并且AQS的狀態(tài)值為1,然后直接返回髓堪,如當(dāng)前線程之前已經(jīng)獲取過(guò)該鎖送朱,則這次簡(jiǎn)單的把AQS的狀態(tài)值加1.如果該鎖已經(jīng)被其他線程所擁有的娘荡,則調(diào)用該方法的線程會(huì)被放入到AQS阻塞隊(duì)列阻塞掛起。

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

在如上的代碼中驶沼,ReentrantLock的lock()委托給了sync類(lèi)炮沐,根據(jù)創(chuàng)建的ReentrantLock構(gòu)造函數(shù)選擇實(shí)現(xiàn)的是NonfairSync還是FairSync,這個(gè)鎖是一個(gè)非公平鎖還是公平鎖回怜。這里先看sync子類(lèi)NonfairSync的情況也就是非公平鎖

非公平鎖

final void lock(){
    //1.CAS設(shè)置當(dāng)前值
    if(compareAndSet(0,1)){
        setExclusiveOwnerThread(Thread.currentThread)大年;
    }else{
    //2.調(diào)用AQS的acquire方法
        acquire(1);
    }
}

在代碼1中,因?yàn)槟J(rèn)AQS的狀態(tài)值為0鹉戚,所以第一個(gè)調(diào)用Lock的線程會(huì)通過(guò)CAS設(shè)置狀態(tài)值為1,CAS成功則表示當(dāng)前線程獲取到了鎖专控,然后setExclusiveOwnerThread設(shè)置該鎖的擁有者為當(dāng)前線程抹凳。
如果這時(shí)候有其他線程調(diào)用lock方法企圖獲取該鎖,CAS會(huì)失敗伦腐,然后會(huì)調(diào)用AQS的acquire方法赢底。注意,傳遞參數(shù)為1柏蘑。

 public final void acquire(int arg) {
    //調(diào)用ReentrantLock重寫(xiě)的tryAcquire方法幸冻。
        if (!tryAcquire(arg) &&
            //tryAcquire返回false會(huì)把當(dāng)前線程放入AQS阻塞隊(duì)列里面
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

AQS并沒(méi)有提供可用的tryAcquire方法,tryAcquire方法需要子類(lèi)自己定制化咳焚,所以這里代碼3會(huì)調(diào)用ReentrantLock自己重寫(xiě)的tryAcquire方法洽损。

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
         @ReservedStackAccess
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //4當(dāng)前AQS的狀態(tài)值為0
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //5當(dāng)前線程是否是該鎖的持有者
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }//6
            return false;
        }

首先代碼4會(huì)查看當(dāng)前鎖的狀態(tài)是否為0,為0則說(shuō)明當(dāng)前鎖空閑革半,那么就嘗試CAS獲取該鎖碑定,將AQS的狀態(tài)值從0設(shè)置為1,并設(shè)置當(dāng)前鎖的持有者為當(dāng)前線程然后但會(huì)true又官,如果當(dāng)前狀態(tài)不為0則說(shuō)明該鎖已經(jīng)被某個(gè)線程所持有延刘,所以代碼首先判斷當(dāng)前線程是否為該鎖的持有者,如果是則狀態(tài)值加1六敬,然后返回true碘赖,這里需要注意,nextc<0代表可重入次數(shù)溢出了外构。如果當(dāng)前線程不是鎖的持有者則返回false普泡,然后其會(huì)被放入AQS阻塞隊(duì)列。
介紹完了非公平鎖的實(shí)現(xiàn)代碼审编,再來(lái)關(guān)注非公平在這里是如何實(shí)現(xiàn)的劫哼。
首先非公平鎖是說(shuō)先嘗試獲取鎖的線程并不一定比后嘗試獲取鎖的線程優(yōu)先獲取鎖
這里假設(shè)線程A調(diào)用了lock方法執(zhí)行到nonfairTryAcquire的代碼4,發(fā)現(xiàn)當(dāng)前狀態(tài)值不為0則執(zhí)行代碼5割笙,發(fā)現(xiàn)當(dāng)前線程不是線程的持有者权烧,則執(zhí)行代碼6返回false,然后線程A被放入AQS阻塞隊(duì)列眯亦。
這時(shí)候線程B也調(diào)用了lock()方法執(zhí)行到nonfairTryAcquire的代碼4方法,發(fā)現(xiàn)當(dāng)前狀態(tài)值為0了(假設(shè)占有該鎖的其他線程釋放了該鎖)般码,所以通過(guò)CAS設(shè)置獲取到了該鎖妻率。明明是線程A先請(qǐng)求獲取該鎖的啊板祝?這就是非公平的體現(xiàn)宫静。
這里的線程B,在獲取鎖之前并沒(méi)有查看當(dāng)前AQS隊(duì)列里面是否有比自己更早請(qǐng)求該鎖的線程券时,而是采用了搶奪策略孤里。

公平鎖

 @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //7.當(dāng)前AQS狀態(tài)值為0
            if (c == 0) {
            //8.公平策略 
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //9.當(dāng)前線程是該鎖持有者
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

如上的代碼所示,公平鎖tryAcquire策略與非公平鎖類(lèi)似橘洞,不同之處策略捌袜,代碼8在設(shè)置CAS之前添加了hasQueuedPredecessors方法,該方法是實(shí)現(xiàn)公平性的核心代碼

 public final boolean hasQueuedPredecessors() {
       Node t=tail;
       Node h=head;
       Node s;
       return h!=t&&((s=h.text)==null||s.thread!=Thread.currentThread());
    }

在如上的代碼中炸枣,如果當(dāng)前線程節(jié)點(diǎn)有前驅(qū)節(jié)點(diǎn)則返回true虏等,否則如果當(dāng)前AQS隊(duì)列為空或者當(dāng)前線程節(jié)點(diǎn)是AQS第一個(gè)節(jié)點(diǎn)則返回false。如果h==t則說(shuō)明當(dāng)前AQS隊(duì)列為空适肠,直接返回false霍衫;如果h!=t并且s==null則說(shuō)明有一個(gè)元素將要作為AQS的第一個(gè)節(jié)點(diǎn)加入隊(duì)列(enq函數(shù)的第一個(gè)元素入隊(duì)列是兩步操作:首先常見(jiàn)一個(gè)哨兵頭結(jié)點(diǎn),然后將第一個(gè)元素插入哨兵節(jié)點(diǎn)后面)侯养,那么返回true敦跌,如果h!=t和s.thread!=Thread.currentThread()則說(shuō)明隊(duì)列里面的第一個(gè)元素不是當(dāng)前線程逛揩,則返回true峰髓。

void lockInterruptibly()方法

這個(gè)方法與lock()方法類(lèi)似,不同在于息尺,它對(duì)中斷進(jìn)行響應(yīng)携兵,就是當(dāng)前線程在調(diào)用該方法時(shí),如果其他線程調(diào)用了當(dāng)前線程的interrupt()方法搂誉,則當(dāng)前線程會(huì)拋出中斷異常InterruptedException異常徐紧,然后返回。

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
     public final void acquireInterruptibly(int arg)
            throws InterruptedException {
            //如果當(dāng)前線程被中斷炭懊,則直接拋出異常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

boolean tryLock()方法

嘗試獲取鎖并级,如果當(dāng)前鎖沒(méi)有被其他線程持有,則當(dāng)前線程獲取該鎖并返回true侮腹,否則返回false嘲碧。這方法不會(huì)引起線程阻塞

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
     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) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

boolean tryLock(long timeout,TimeUnit unit)

嘗試獲取鎖,與tryLock()的不同之處在于父阻,它設(shè)置了超時(shí)時(shí)間愈涩,如果超時(shí)時(shí)間沒(méi)有獲取到了該鎖則返回false

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

釋放鎖 void unlock()方法

嘗試釋放鎖望抽,如果當(dāng)前線程持有該鎖,則調(diào)用該方法會(huì)讓該線程對(duì)持有AQS state狀態(tài)值減1履婉,如果減去1之后狀態(tài)值變?yōu)?狀態(tài)值為0煤篙,則當(dāng)前線程會(huì)釋放該鎖,否則僅僅減1而已毁腿。如果當(dāng)前線程沒(méi)有持有該鎖則會(huì)拋出IllegalMonitorStateException異常

    public void unlock() {
        sync.release(1);
    }
    @ReservedStackAccess
        protected final boolean tryRelease(int releases) {
        //11.如果不是鎖持有者調(diào)用unlock則拋出Unlock異常
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //12.如果當(dāng)前的可重入次數(shù)為0辑奈,則從孔該鎖的持有線程
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //13.設(shè)置可重入次數(shù)為原始值-1
            setState(c);
            return free;
        }

如上代碼所示,如果當(dāng)前線程不是該鎖持有者則直接拋出異常已烤,否則查看狀態(tài)值是否為0鸠窗,為0則說(shuō)明當(dāng)前線程要放棄對(duì)該鎖的持有權(quán),則執(zhí)行代碼12把鎖的持有者為null胯究,如果狀態(tài)值不為0稍计,則僅僅讓當(dāng)前線程對(duì)該鎖的可重入次數(shù)減1.

使用ReentrantLock實(shí)現(xiàn)一個(gè)簡(jiǎn)單的線程安全的list;

public static class ReentrantLockList {
    //線程不安全的List
    private ArrayList<String> array=new ArrayList<>();
    //獨(dú)占鎖
    private volatile ReentrantLock lock=new ReentrantLock();
    //添加元素
    public void add(String e){
        lock.lock();
        try{
            array.add(e);
        }finally {
            lock.unlock();
        }
    }
    //刪除元素
    public void remove(String e){
        lock.lock();
        try{
            array.remove(e);
        }finally {
            lock.unlock();
        }
    }
    //獲取數(shù)據(jù)
    public String get(int index){
        lock.lock();
        try{
            return array.get(index);
        }finally {
            lock.unlock();
        }
    }
}

如上代碼通過(guò)操作array元素前進(jìn)行加鎖保證了同一時(shí)間只有一個(gè)線程可以對(duì)arry進(jìn)行修改唐片,但是也只能有一個(gè)線程對(duì)array元素進(jìn)行訪問(wèn)丙猬。

如圖所示涨颜,假如線程Thread1,Thread2,Thread3同時(shí)嘗試獲取獨(dú)占鎖ReentrantLock费韭,假如Thread1獲取到了,那么Thread2和Thread3就會(huì)被轉(zhuǎn)換為Node 節(jié)點(diǎn)被放入ReentrantLock的AQS阻塞隊(duì)列庭瑰,而后被阻塞掛起星持。


如圖所示,假設(shè)Thread1獲取該鎖了之后調(diào)用了對(duì)應(yīng)鎖創(chuàng)建的條件變量1 await()方法,那么Thread1就會(huì)釋放獲取到的鎖,然后當(dāng)前線程就會(huì)被轉(zhuǎn)換為Node節(jié)點(diǎn)插入條件變量1的條件隊(duì)列帆调,由于Thread1釋放了鎖闪萄,鎖以阻塞到AQS隊(duì)列里面的Thread2和Thread3就有機(jī)會(huì)獲取到所=鎖,假如使用的公平策略玫膀,那么這時(shí)候Thread2會(huì)獲取到該鎖,從而從AQS隊(duì)列里面一出Thread2對(duì)應(yīng)的Node節(jié)點(diǎn)。

讀寫(xiě)鎖ReentrantReadWriteLock原理

類(lèi)圖結(jié)構(gòu)

1202638-20180614135056420-1667937320.png

解決線程安全問(wèn)題其實(shí)使用ReentrantLock就可以八回,但是ReentrantLock是獨(dú)占鎖,某時(shí)只有一個(gè)線程就可以獲取該鎖驾诈,而實(shí)際上會(huì)有寫(xiě)少讀多的場(chǎng)景缠诅,顯然ReentrantLock滿(mǎn)足不了需求,所以ReentrantReadWriteLock應(yīng)運(yùn)而生乍迄。ReentrantReadWriteLock采用讀寫(xiě)分離的策略管引,允許讀個(gè)線程可以同時(shí)獲取讀鎖。

讀寫(xiě)鎖的內(nèi)部維護(hù)了一個(gè)ReadLock和WriteLock,他們依賴(lài)Sync實(shí)現(xiàn)具體的功能闯两,而Sync繼承自AQS,并且也提供了公平鎖和非公平鎖的實(shí)現(xiàn)褥伴。我們知道AQS中值維護(hù)了一個(gè)state狀態(tài)谅将,一個(gè)state怎么表示讀和寫(xiě)兩種狀態(tài)呢?ReentrantReadWriteLock巧妙的運(yùn)用state的高16位表示讀狀態(tài)噩翠,也就是獲取到讀鎖的次數(shù);使用低16位表示獲取到寫(xiě)鎖的線程的可重入次數(shù)戏自。

 static final int SHARED_SHIFT   = 16;
        //讀鎖(共享鎖)狀態(tài)單位指65536
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        //讀鎖(共享鎖)線程最大的個(gè)數(shù)65535
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        //寫(xiě)鎖(排它鎖)掩碼,二進(jìn)制 15個(gè)1
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
        //返回讀鎖線程數(shù)
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        //返回寫(xiě)鎖可重入個(gè)數(shù)
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

寫(xiě)鎖的獲取與釋放

在ReentrantReadWriteLock中寫(xiě)鎖使用writeLock來(lái)實(shí)現(xiàn)

void lock()

寫(xiě)鎖是一個(gè)獨(dú)占鎖伤锚,只有一個(gè)線程可以獲取資源擅笔。如果當(dāng)前沒(méi)有線程獲取到讀鎖和寫(xiě)鎖,則當(dāng)前線程可以獲取到寫(xiě)鎖然后返回屯援。如果當(dāng)前已有線程獲取讀鎖或者寫(xiě)鎖猛们,則當(dāng)前請(qǐng)求寫(xiě)鎖的線程就會(huì)被阻塞掛起。另外狞洋,寫(xiě)鎖是可重入鎖弯淘,如果當(dāng)前線程已經(jīng)獲取了該鎖,再次獲取知識(shí)簡(jiǎn)單的把可重入次數(shù)加1然后直接返回吉懊。

    public void lock() {
            sync.acquire(1);
        }
        public final void acquire(int arg) {
        //syn重寫(xiě)tryAcquire方法
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

非公平寫(xiě)鎖的lock內(nèi)部調(diào)用了AQS的acquire的方法庐橙,其中tryAcquire是ReentrantReadWriteLock內(nèi)部類(lèi)sync類(lèi)重寫(xiě)的。

protected final boolean tryAcquire(int acquires) {
    
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            //1.說(shuō)明讀鎖或者寫(xiě)鎖已經(jīng)被謀線程獲取
            if (c != 0) {
            //2 w=0說(shuō)明已經(jīng)有線程獲取了讀鎖借嗽,w!=0表示當(dāng)前線程不是寫(xiě)鎖擁有者
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                    //3.說(shuō)明當(dāng)前線程獲取了寫(xiě)鎖态鳖,判斷可重入次數(shù)
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 4.設(shè)置可重入次數(shù)+1
                setState(c + acquires);
                return true;
            }
            //5.第一個(gè)寫(xiě)線程獲取寫(xiě)鎖
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

在代碼1中,如果當(dāng)前AQS的狀態(tài)值不為0則說(shuō)明當(dāng)前已經(jīng)有線程獲取到了讀鎖或者寫(xiě)鎖恶导。在代碼2中浆竭,如果w==0說(shuō)明狀態(tài)值的低16位為0,而AQS狀態(tài)值不為0惨寿,則說(shuō)明高16位不為0邦泄,這暗示已經(jīng)有線程獲取到讀鎖,所以直接返回false裂垦;
而如果w!=0則說(shuō)明已有線程獲取了寫(xiě)鎖顺囊,在看當(dāng)前線程是不是該鎖的持有者,如果過(guò)不是就返回false蕉拢;
執(zhí)行到代碼3說(shuō)明當(dāng)前線程之前已經(jīng)獲取了讀寫(xiě)鎖特碳,所以判斷該線程的可重入數(shù)是不是超過(guò)了最大值,是則拋出異常企量,否則執(zhí)行代碼4增加當(dāng)前線程的可重入次數(shù)测萎,然后返回true。
如果AQS的狀態(tài)值為0則表示目前沒(méi)有線程獲取到讀鎖和寫(xiě)鎖届巩,所以執(zhí)行代碼5硅瞧,搶占式執(zhí)行CAS嘗試獲取寫(xiě)鎖,獲取成功則設(shè)置當(dāng)前鎖的持有者為當(dāng)前線程并返回true恕汇,否則返回false腕唧;
公平鎖的實(shí)現(xiàn)為:

  final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }

這里還是使用hasQueuedPredecessors判斷當(dāng)前線程節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)或辖,如果有則當(dāng)前線程放棄獲取寫(xiě)鎖的權(quán)利,直接返回false枣接;

void lockInterruptibly()

類(lèi)似于lock()方法颂暇,它的不同之處在于,他會(huì)對(duì)中斷做出響應(yīng)但惶,也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí)耳鸯,其會(huì)拋出InterruptedException

 public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }

Boolean trylock()

嘗試獲取寫(xiě)鎖,如果當(dāng)前沒(méi)有其他線程持有寫(xiě)鎖或者讀鎖膀曾,則當(dāng)前線程獲取寫(xiě)鎖會(huì)成功县爬,然后返回true,如果有返回false添谊,但是當(dāng)前線程并不會(huì)阻塞财喳。如果當(dāng)前線程已經(jīng)持有了讀寫(xiě)鎖后則簡(jiǎn)單增加AQS的狀態(tài)值后直接返回true。

 public boolean tryLock() {
            return sync.tryWriteLock();
        }
        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

boolean tryLock(long timeout, TimeUnit unit)

與tryAcquire的不同之處在于斩狱,多了超時(shí)時(shí)間參數(shù)耳高,如果嘗試獲取寫(xiě)鎖失敗則會(huì)把當(dāng)前線程掛起指定的時(shí)間,帶到超時(shí)時(shí)間到后當(dāng)前線程被激活所踊,如果還是沒(méi)有獲取到寫(xiě)鎖則返回false泌枪。另外,該方法會(huì)對(duì)中斷進(jìn)行響應(yīng)污筷,也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí)工闺,其會(huì)拋出InterruptedException

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

void unlock()

嘗試釋放鎖乍赫,如果當(dāng)前線程持有該鎖瓣蛀,調(diào)用該方法會(huì)讓該線程對(duì)該線程持有的AQS狀態(tài)值減1,如果減去1后當(dāng)前狀態(tài)值為0則當(dāng)前線程會(huì)釋放該鎖雷厂,否則僅僅是減1而已惋增。如果當(dāng)前線程沒(méi)有持有該鎖而調(diào)用了這個(gè)方法則會(huì)拋出IllegalMonitor

    public void unlock() {
            sync.release(1);
        }
        
        
    public final boolean release(int arg) {
        //調(diào)用ReentrantReadWriteLock中sync實(shí)現(xiàn)的tryRelease方法
        if (tryRelease(arg)) {
            //激活阻塞隊(duì)列里面的一個(gè)線程
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//LockSupport里面的Unpark方法
            return true;
        }
        return false;
    }
        
        
    protected final boolean tryRelease(int releases) {
        //看是否是寫(xiě)鎖擁有者調(diào)用的unlock
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //獲取可重入值,這里沒(méi)有高16位改鲫,因?yàn)楂@取寫(xiě)鎖時(shí)讀鎖的狀態(tài)肯定為0
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            //如果寫(xiě)鎖可重入值為0則釋放鎖诈皿,否則只是簡(jiǎn)單的更新?tīng)顟B(tài)值。
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

讀鎖的獲取與釋放

ReentrantReadWriteLock中的讀鎖時(shí)使用ReadLock來(lái)實(shí)現(xiàn)的

void lock()

獲取讀鎖像棘,如果當(dāng)前沒(méi)有其他線程持有寫(xiě)鎖稽亏,則當(dāng)前線程可以獲取讀鎖,AQS狀態(tài)指的高16位的值會(huì)增加1缕题,然后方法返回截歉,否則如果其他一個(gè)線程持有寫(xiě)鎖,則當(dāng)前線程就會(huì)被阻塞烟零。

    public void lock() {
            sync.acquireShared(1);
        }
        
        public final void acquireShared(int arg) {
        //調(diào)用ReentrantReadWriteLock中sync實(shí)現(xiàn)的tryAcquireShared方法瘪松。
        if (tryAcquireShared(arg) < 0)
        //調(diào)用AQS的doAcquireShared方法
            doAcquireShared(arg);
    }

在如上的代碼中咸作,讀鎖的lock方法調(diào)用了AQS的acquireShared方法,在其內(nèi)部調(diào)用了ReentrantReadWriteLock中的sync重寫(xiě)的tryAcquireShared方法宵睦。

    protected final int tryAcquireShared(int unused) {
            //1.獲取當(dāng)前線程的狀態(tài)值
            Thread current = Thread.currentThread();
            int c = getState();
            //2.判斷是否被寫(xiě)鎖占用
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //3獲取讀鎖計(jì)數(shù)
            int r = sharedCount(c);
            //4.嘗試獲取鎖记罚,讀個(gè)讀線程只有一個(gè)會(huì)成功,不成功的會(huì)進(jìn)入fullTryAcquireShared進(jìn)行重試
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                //5.第一個(gè)線程獲取讀鎖
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                //6.如果當(dāng)前線程是第一個(gè)獲取讀鎖的線程
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                //7.記錄最后一個(gè)獲取讀鎖的線程獲記錄其他線程讀鎖的可重入數(shù)壳嚎。
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null ||
                        rh.tid != LockSupport.getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //類(lèi)似于tryAcquireShared
            return fullTryAcquireShared(current);
        }

如上的代碼首先獲取了當(dāng)前AQS的狀態(tài)值桐智,然后代碼2查看是否有其他線程獲取到了寫(xiě)鎖,如果是則直接返回-1烟馅,而后調(diào)用AQS的doAcquireShared方法把當(dāng)前線程放入AQS阻塞隊(duì)列酵使。
如果當(dāng)前要獲取讀鎖的線程之前已經(jīng)持有了寫(xiě)鎖,則也可以獲取讀鎖焙糟,但是需要注意口渔,一個(gè)線程先獲取了寫(xiě)鎖,然后獲取了讀鎖處理事情完畢后穿撮,要記得把讀鎖和寫(xiě)鎖一起釋放掉缺脉,不能只釋放寫(xiě)鎖。
否則執(zhí)行代碼3悦穿,得到獲取到的讀鎖的個(gè)數(shù)攻礼,到這里就說(shuō)明沒(méi)有線程獲取到寫(xiě)鎖,但是可能有線程持有讀鎖栗柒,然后執(zhí)行代碼4礁扮,其中非公平鎖的readerShouldBlock實(shí)現(xiàn)代碼

        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
         final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

如上代碼的作用是,如果隊(duì)列里面存在一個(gè)元素瞬沦,則判斷第一個(gè)元素是不是正在嘗試獲取寫(xiě)鎖太伊,若不是,則當(dāng)前線程判斷當(dāng)前獲取讀鎖的線程是否已經(jīng)達(dá)到了最大值逛钻,最后執(zhí)行CAS操作將AQS狀態(tài)值的高16位加1.
代碼5僚焦,6記錄第一個(gè)獲取讀鎖的線程并統(tǒng)計(jì)該線程獲取讀鎖的可重入數(shù)。代碼7cachedHoldCounter記錄最后一個(gè)獲取到讀鎖的線程和該線程獲取讀鎖的可重入數(shù)曙痘,readHolds記錄了當(dāng)前線程獲取讀鎖的可重入數(shù)芳悲。
如果readerShouldBlock返回true則代表有線程正在獲取寫(xiě)鎖,所以執(zhí)行代碼8边坤,fullTryAcquireShared和tryAcquireShared類(lèi)似名扛,但是fullTryAcquireShared是通過(guò)自選獲取。

void lockInterruptibly()

類(lèi)似于lock()方法茧痒,它的不同之處在于肮韧,他會(huì)對(duì)中斷做出響應(yīng),也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí),其會(huì)拋出InterruptedException

boolean tryLock()

嘗試獲取寫(xiě)鎖惹苗,如果當(dāng)前沒(méi)有其他線程持有寫(xiě)鎖殿较,則當(dāng)前線程獲取讀鎖會(huì)成功,然后返回true桩蓉,如果有返回false淋纲,但是當(dāng)前線程并不會(huì)阻塞。如果當(dāng)前線程已經(jīng)持有了讀鎖后則簡(jiǎn)單增加AQS的狀態(tài)值高16位后直接返回true院究。

boolean tryLock(long timeout,TimeUnit unit)

與tryLock()的不同之處在于洽瞬,多了超時(shí)時(shí)間參數(shù),如果嘗試獲取讀鎖失敗則會(huì)把當(dāng)前線程掛起指定的時(shí)間业汰,帶到超時(shí)時(shí)間到后當(dāng)前線程被激活伙窃,如果還是沒(méi)有獲取到讀鎖則返回false。另外样漆,該方法會(huì)對(duì)中斷進(jìn)行響應(yīng)为障,也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí),其會(huì)拋出InterruptedException

void unlock()

public void unlock() {
            sync.releaseShared(1);
        }

如上代碼具體釋放鎖的操作是委托給Sync類(lèi)來(lái)做的

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    
    protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
        //循環(huán)知道自己的讀計(jì)數(shù)-1放祟,CAS更新成功鳍怨。
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

如以上代碼所示,在無(wú)線循環(huán)里面跪妥,首先會(huì)獲取當(dāng)前的AQS狀態(tài)值并將其保存到變量c鞋喇,然后變量c被減去一個(gè)讀計(jì)數(shù)單位后使用CAS去更新AQS的狀態(tài)值,如果更新成功則查看當(dāng)前的AQS的狀態(tài)值是否為0眉撵,為0則說(shuō)明當(dāng)前已經(jīng)沒(méi)有讀線程占用該鎖侦香,則tryReleaseShared返回try。然后會(huì)調(diào)用doReleaseShared方法釋放一個(gè)由于獲取寫(xiě)鎖而被阻塞的線程纽疟,如果當(dāng)前AQS的狀態(tài)值不為0罐韩,則說(shuō)明還有其他線程持有了讀鎖,所以tryReleaseShared返回false仰挣。如果tryReleaseShared中的CAS更新AQS狀態(tài)值失敗伴逸,則自旋重試直到成功缠沈。

基于ReentrantReadWriteLock實(shí)現(xiàn)線程安全的list

之前使用ReentrantLock實(shí)現(xiàn)線程安全的list膘壶,但是由于ReentrantLock是獨(dú)占鎖,所以在讀多寫(xiě)少的情況下性能很差洲愤。

public class ReentrantReadWriteLockList {
    //線程不安全的List
    private ArrayList<String> array=new ArrayList<>();
    //獨(dú)占鎖
    private volatile ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    private final Lock readLock=lock.readLock();
    private final Lock writeLock=lock.writeLock();
    
    //添加元素
    public void add(String e){
        writeLock.lock();
        try{
            array.add(e);
        }finally {
            writeLock.unlock();
        }
    }
    
    //刪除元素
    public void remove(String e){
        writeLock.lock();
        try{
            array.remove(e);
        }finally {
            writeLock.unlock();
        }
    }
    //獲取數(shù)據(jù)
    public String get(int index){
        readLock.lock();
        try{
            return array.get(index);
        }finally {
            readLock.unlock();
        }
    }
}

以上代碼調(diào)用get的時(shí)候用的是讀鎖颓芭,這樣運(yùn)行多個(gè)讀線程來(lái)同時(shí)訪問(wèn)list的元素,這在讀多寫(xiě)少的情況下性能會(huì)更好柬赐。


參考資料:
《Java并發(fā)編程之美》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亡问,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌州藕,老刑警劉巖束世,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異床玻,居然都是意外死亡毁涉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)锈死,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贫堰,“玉大人,你說(shuō)我怎么就攤上這事待牵∑淦粒” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵缨该,是天一觀的道長(zhǎng)偎行。 經(jīng)常有香客問(wèn)我,道長(zhǎng)贰拿,這世上最難降的妖魔是什么睦优? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮壮不,結(jié)果婚禮上汗盘,老公的妹妹穿的比我還像新娘。我一直安慰自己询一,他們只是感情好隐孽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著健蕊,像睡著了一般菱阵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缩功,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天晴及,我揣著相機(jī)與錄音,去河邊找鬼嫡锌。 笑死虑稼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的势木。 我是一名探鬼主播蛛倦,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼啦桌!你這毒婦竟也來(lái)了溯壶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎且改,沒(méi)想到半個(gè)月后验烧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡又跛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年噪窘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片效扫。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倔监,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出菌仁,到底是詐尸還是另有隱情浩习,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布济丘,位于F島的核電站谱秽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏摹迷。R本人自食惡果不足惜疟赊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峡碉。 院中可真熱鬧近哟,春花似錦、人聲如沸鲫寄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)地来。三九已至戳玫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間未斑,已是汗流浹背咕宿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜡秽,地道東北人府阀。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像载城,于是被迫代替她去往敵國(guó)和親肌似。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容