ReentrantLock原理剖析

引言

提起java的線程同步敬辣,大家總能想到sychronized關(guān)鍵字。sychronized是由JVM提供的重量級(jí)鎖溉跃,使用方式簡(jiǎn)單撰茎,功能比較單一。
ReentrantLock是由java API提供的用來做線程同步的類,它的實(shí)現(xiàn)借助了隊(duì)列同步器AQS(AbstractQueuedSynchronizer)募疮。

AQS 是 Java 并發(fā)包中實(shí)現(xiàn)鎖僻弹、同步的一個(gè)重要基礎(chǔ)框架蹋绽。

ReentrantLock提供了比sychronized更豐富的功能,包括可重入卸耘、公平鎖蚣抗、可中斷等。

ReentrantLock使用

ReentrantLock的使用姿勢(shì)如下

private ReentrantLock lock = new ReentrantLock();
public void run() {
    lock.lock();
    try {
        //do bussiness
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

公平鎖

實(shí)例化ReentrantLock時(shí)设哗,可以通過構(gòu)造器傳參指定聲明的是公平鎖還是非公平鎖两蟀。

Lock lock=new ReentrantLock(true);//公平鎖
Lock lock=new ReentrantLock(false);//非公平鎖

公平鎖是指線程獲取鎖的順序是按照加鎖順序來的,而非公平鎖則可以搶鎖战虏,先申請(qǐng)的線程不一定先獲得鎖党涕。
由于公平鎖需要維護(hù)一個(gè)隊(duì)列,記錄當(dāng)前已排隊(duì)的線程手趣,所以非公平鎖的性能會(huì)高于公平鎖肥荔。默認(rèn)構(gòu)造函數(shù)創(chuàng)建的就是非公平鎖。

public ReentrantLock() {
     sync = new NonfairSync();
}

tryLock()

tryLock的功能非常實(shí)用中符,實(shí)際業(yè)務(wù)中的線程等待鎖資源釋放不是永無止境誉帅。通過tryLock形參可以設(shè)定等待時(shí)間右莱,時(shí)間到后如果能夠獲取鎖就返回true慢蜓,否則返回false阀捅,而不是一直阻塞直到等待的鎖資源被釋放。

Lock lock = new ReentrantLock();
@Override
public void run() {
    try {
        lock.tryLock(100, TimeUnit.MILLISECONDS);
    } finally {
        lock.unlock();
    }
}

ReentrantLock原理

查看源碼凄诞,可以看到ReentrantLock類包含了3個(gè)內(nèi)部類:

  • Sync:其中抽象內(nèi)部類Sync繼承自AQS忍级,實(shí)現(xiàn)最核心的功能。
  • FairSync:繼承自抽象類Sync汛蝙,實(shí)現(xiàn)公平鎖
  • NonfairSync:繼承自抽象類Sync朴肺,實(shí)現(xiàn)非公平鎖
源碼.png

加鎖解析

公平鎖

ReentrantLock的lock()方法調(diào)用的是Sync的lock方法戈稿,lock()是抽象方法,具體實(shí)現(xiàn)由其子類FairSync的lock方法提供需了。

//ReentrantLock的lock方法
public void lock() {
    sync.lock();
}

//FairSync的lock方法
final void lock() {
    acquire(1);
}

acquire方法來自Sync類

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire方法的公平鎖實(shí)現(xiàn)如下

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            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;
}

state字段為0表示沒有被任何線程持有般甲,緊接著調(diào)用hasQueuedPredecessors方法判斷隊(duì)列中是否有其他線程正在排隊(duì)

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

如果沒有排隊(duì)的線程敷存,就調(diào)用CAS的compareAndSetState方法線程安全的將同步器的state字段設(shè)置為1。接下來調(diào)用setExclusiveOwnerThread方法把當(dāng)前線程保存下來觅闽,以便下次該線程的重入挽牢。
state大于0意味著當(dāng)前鎖已經(jīng)被線程持有摊求,需要判斷持有鎖是否當(dāng)前線程。
如果是當(dāng)前線程睹栖,將state值加一重新寫入state狀態(tài)(線程可重入)。
如果不是當(dāng)前線程恼除,直接返回失敗曼氛。

acquire方法中,tryAcquire方法返回false會(huì)繼續(xù)調(diào)用addWaiter(Node.EXCLUSIVE), arg)方法將當(dāng)前線程構(gòu)建成Node對(duì)象寫入到線程排隊(duì)隊(duì)列徽级。

AQS中的線程隊(duì)列是由Node對(duì)象為節(jié)點(diǎn)的雙向鏈表聊浅。Node節(jié)點(diǎn)包含兩種模式:排他模式與共享模式低匙。ReentrantLock是排他模式,ReadWriteLock是共享模式顽冶。

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

如果該隊(duì)列已經(jīng)有node(tail!=null)渗稍,則將新節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)置為tail,再通過CAS將tail指向當(dāng)前節(jié)點(diǎn)报强,前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)拱燃,然后返回當(dāng)前節(jié)點(diǎn)。
如果隊(duì)列為空或者CAS失敗召嘶,則通過enq入隊(duì)哮缺。
進(jìn)隊(duì)列的時(shí)候,要么是第一個(gè)入隊(duì)并且設(shè)置head節(jié)點(diǎn)并且循環(huán)設(shè)置tail铛只,要么是add tail,如果CAS不成功直撤,則會(huì)無限循環(huán)蜕着,直到設(shè)置成功,即使高并發(fā)的場(chǎng)景蓖乘,也最終能夠保證設(shè)置成功悄雅,然后返回包裝好的node節(jié)點(diǎn)。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

首先根據(jù) node.predecessor() 獲取到上一個(gè)節(jié)點(diǎn)是否為頭節(jié)點(diǎn)众眨,如果是則嘗試獲取一次鎖娩梨,獲取成功就完成任務(wù)了览徒。
如果不是頭節(jié)點(diǎn),或者獲取鎖失敗纽什,則會(huì)根據(jù)上一個(gè)節(jié)點(diǎn)的waitStatus狀態(tài)來處理躲叼。shouldParkAfterFailedAcquire(p,node) 返回當(dāng)前線程是否需要掛起,如果需要?jiǎng)t調(diào)用 parkAndCheckInterrupt()让蕾。

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

parkAndCheckInterrupt方法使用LockSupport的park方法掛起線程或听。

非公平鎖

//ReentrantLock的lock方法
public void lock() {
    sync.lock();
}

//NonfairSync的lock方法
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

非公平鎖直接調(diào)用compareAndSetState方法修改state,修改成功即成功獲得鎖誉裆,將當(dāng)前線程寫入排他鎖獨(dú)占線程。如果獲取鎖失敗粱腻,則調(diào)用Sync的acquire方法,非公平鎖的tryAcquire方式實(shí)現(xiàn)如下:

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;
}

非公平鎖的邏輯相對(duì)簡(jiǎn)單,不需要判斷線程隊(duì)列直接嘗試獲得鎖遇革。

釋放鎖解析

公平鎖和非公平鎖的釋放流程都是一樣的揭糕。

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;
}

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;
}

總結(jié)

由于公平鎖維護(hù)了線程隊(duì)列著角,加鎖的流程比較繁瑣導(dǎo)致性能開銷大。實(shí)際使用中可優(yōu)先考慮非公平鎖奄容,以獲取更好的性能優(yōu)勢(shì)产徊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舟铜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谆刨,更是在濱河造成了極大的恐慌痊夭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件难捌,死亡現(xiàn)場(chǎng)離奇詭異鸦难,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)击敌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門沃斤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徘公,你說我怎么就攤上這事哮针。” “怎么了十厢?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蛮放,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瞻想,道長(zhǎng)娩嚼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任漠其,我火速辦了婚禮和屎,結(jié)果婚禮上春瞬,老公的妹妹穿的比我還像新娘。我一直安慰自己宽气,他們只是感情好萄涯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枣察,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臂痕。 梳的紋絲不亂的頭發(fā)上猿涨,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天叛赚,我揣著相機(jī)與錄音,去河邊找鬼红伦。 笑死昙读,一個(gè)胖子當(dāng)著我的面吹牛膨桥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沮稚,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼册舞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了盛杰?” 一聲冷哼從身側(cè)響起藐石,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤于微,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后驱证,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恋腕,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吗坚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年呆万,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谋减。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扫沼。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缎除,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出器罐,到底是詐尸還是另有隱情轰坊,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布粟害,位于F島的核電站颤芬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏站蝠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望豌习。 院中可真熱鬧,春花似錦既荚、人聲如沸栋艳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)兼蕊。三九已至,卻和暖如春孙技,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牵啦。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工亚情, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哈雏。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓楞件,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親僧著。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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