詳解java并發(fā)之重入鎖-ReentrantLock
前言
目前主流的鎖有兩種,一種是synchronized,另一種就是ReentrantLock,JDK優(yōu)化到現(xiàn)在目前為止synchronized的性能已經(jīng)和重入鎖不分伯仲了,但是重入鎖的功能和靈活性要比這個(gè)關(guān)鍵字多的多,所以重入鎖是可以完全替代synchronized關(guān)鍵字的。下面就來介紹這個(gè)重入鎖。
正文
ReentrantLock重入鎖是Lock接口里最重要的實(shí)現(xiàn),也是在實(shí)際開發(fā)中應(yīng)用最多的一個(gè),我這篇文章更接近實(shí)際開發(fā)的應(yīng)用場(chǎng)景,為開發(fā)者提供直接上手應(yīng)用。所以不是所有方法我都講解,有些冷門的方法我不會(huì)介紹或一句帶過。
一、首先先看聲明一個(gè)重入鎖需要使用到那幾個(gè)構(gòu)造方法
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
推薦聲明方式
private static ReentrantLock lock = new ReentrantLock(true); private static ReentrantLock locka = new ReentrantLock();
重點(diǎn)說明:
ReentrantLock提供了兩個(gè)構(gòu)造方法,對(duì)應(yīng)兩種聲明方式。
第一種聲明的是公平鎖,所謂公平鎖,就是按照時(shí)間先后順序,使先等待的線程先得到鎖,而且,公平鎖不會(huì)產(chǎn)生饑餓鎖,也就是只要排隊(duì)等待,最終能等待到獲取鎖的機(jī)會(huì)。
第二種聲明的是非公平鎖,所謂非公平鎖就和公平鎖概念相反,線程等待的順序并不一定是執(zhí)行的順序,也就是后來進(jìn)來的線程可能先被執(zhí)行。
ReentrantLock默認(rèn)是非公平鎖,因?yàn)椋汗芥i實(shí)現(xiàn)了先進(jìn)先出的公平性,但是由于來一個(gè)線程就加入隊(duì)列中,往往都需要阻塞,再由阻塞變?yōu)檫\(yùn)行,這種上下文切換是非常好性能的。非公平鎖由于允許插隊(duì)所以,上下文切換少的多,性能比較好,保證的大的吞吐量,但是容易出現(xiàn)饑餓問題。所以實(shí)際生產(chǎn)也是較多的使用非公平鎖。
非公平鎖調(diào)用的是NonfairSync方法。
二、加入鎖之后lock方法到底是怎么處理的(只講非公平鎖)
剛才我們說如果是非公平鎖就調(diào)用NonfairSync方法,那我們就來看看這個(gè)方法都做來什么。
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
重點(diǎn)說明:
讀前先知:ReentrantLock用state表示“持有鎖的線程已經(jīng)重復(fù)獲取該鎖的次數(shù)”。當(dāng)state(下文用狀態(tài)二子代替)等于0時(shí),表示當(dāng)前沒有線程持有鎖)。
第一步調(diào)用compareAndSetState方法,傳了第一參數(shù)是期望值0,第二個(gè)參數(shù)是實(shí)際值1,當(dāng)前這個(gè)方法實(shí)際是調(diào)用了unsafe.compareAndSwapInt實(shí)現(xiàn)CAS操作的,也就是上鎖之前狀態(tài)必須是0,如果是0調(diào)用setExclusiveOwnerThread方法
private transient Thread exclusiveOwnerThread; protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; }
可以看出setExclusiveOwnerThread就是線程設(shè)置為當(dāng)前線程,此時(shí)說明有一名線程已經(jīng)拿到了鎖。大家都是CAS有三個(gè)值,如果舊值等于預(yù)期值,就把新值賦予上,所以當(dāng)前線程得到了鎖就會(huì)把狀態(tài)置為1。
第二步是compareAndSetState方法返回false時(shí),此時(shí)調(diào)用的是acquire方法,參數(shù)傳1
tryAcquire()方法實(shí)際是調(diào)用了nonfairTryAcquire()方法。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 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; }
注釋上說的很明白,請(qǐng)求獨(dú)占鎖,忽略所有中斷,至少執(zhí)行一次tryAcquire,如果成功就返回,否則線程進(jìn)入阻塞--喚醒兩種狀態(tài)切換中,直到tryAcquire成功。詳情見鏈接tryAcquire()、addWaiter()、acquireQueued()挨個(gè)分析。
好,到日前為止大家清楚了lock()方法到調(diào)用過程,清楚了,為什么只有得到鎖的當(dāng)前線程才可以執(zhí)行,沒有得到的會(huì)在隊(duì)列里不停的利用CAS原理試圖得到鎖,CAS很高效,也就是,為什么ReentrantLock比synchronized高效的原因,缺點(diǎn)是很浪費(fèi)cpu資源。
三、所有線程都執(zhí)行完畢后調(diào)用unlock()方法
unlock()方法是通過AQS的release(int)方法實(shí)現(xiàn)的,我們可以看一下:
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
tryRelease()是由子類實(shí)現(xiàn)的,我們來看一下ReentrantLock中的Sync對(duì)它的實(shí)現(xiàn):
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; }
先通過getState獲得狀態(tài)標(biāo)識(shí),如果這個(gè)標(biāo)識(shí)和要釋放的數(shù)量相等,就會(huì)把當(dāng)前占有鎖的線程設(shè)置為null,實(shí)現(xiàn)鎖的釋放,然后返回true,否則把狀態(tài)標(biāo)識(shí)減去releases再返回false。
以上所述是小編給大家介紹的java并發(fā)之重入鎖-ReentrantLock詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
微服務(wù)?Spring?Boot?整合?Redis?BitMap?實(shí)現(xiàn)?簽到與統(tǒng)計(jì)功能
這篇文章主要介紹了微服務(wù)?Spring?Boot?整合?Redis?BitMap?實(shí)現(xiàn)?簽到與統(tǒng)計(jì)功能,文章簡(jiǎn)單介紹了Redis BitMap 基本用法結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01簡(jiǎn)單理解java泛型的本質(zhì)(非類型擦除)
泛型在java中有很重要的地位,在面向?qū)ο缶幊碳案鞣N設(shè)計(jì)模式中有非常廣泛的應(yīng)用。泛型是參數(shù)化類型的應(yīng)用,操作的數(shù)據(jù)類型不限定于特定類型,可以根據(jù)實(shí)際需要設(shè)置不同的數(shù)據(jù)類型,以實(shí)現(xiàn)代碼復(fù)用。下面小編來簡(jiǎn)單講一講泛型2019-05-05Apache?Log4j2?報(bào)核彈級(jí)漏洞快速修復(fù)方法
Apache?Log4j2?是一個(gè)基于Java的日志記錄工具,是?Log4j?的升級(jí),是目前最優(yōu)秀的?Java日志框架之一,這篇文章主要介紹了突發(fā)Apache?Log4j2?報(bào)核彈級(jí)漏洞快速修復(fù)方法,需要的朋友可以參考下2021-12-12Collections工具類_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Collections工具類提供了大量針對(duì)Collection/Map的操作。這篇文章主要介紹了Collections工具類_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理,需要的朋友可以參考下2017-04-04JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法
這篇文章主要介紹了JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-10-10Java開發(fā)者必備10大數(shù)據(jù)工具和框架
這篇文章主要為大家詳細(xì)介紹了Java開發(fā)者必備10大數(shù)據(jù)工具和框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06