JUC之ReentrantLock源碼閱讀

ReentrantLock是juc包里的一種重要的鎖擅羞∪卤ぃ可重入鎖蝗罗,顧名思義,就是一個線程可以重復(fù)進入該鎖所保護的臨界資源蝌戒。下面通過源碼閱讀串塑,來一步一步看是怎么實現(xiàn)的。

uml圖

ReentrantLock

ReentrantLock實現(xiàn)了Lock和serializable接口北苟,同時其主要操作委托給其內(nèi)部類Sync來執(zhí)行桩匪。Sync有兩個子類FairSync和NonfairSync,即公平鎖和非公平鎖友鼻。Sync同時又繼承了AbstractQueuedSynchronizer,java中的一個重要的類傻昙,簡稱AQS。該類主要維護一個雙向的鏈表彩扔,表的Node為等待獲取鎖的線程妆档。所有新入隊的Node都在表為,即tail所在的位置虫碉。所有自行的線程贾惦,都放在表頭,即head所指的位置。

一個例子進入學(xué)習(xí)

import java.util.concurrent.locks.ReentrantLock;

public class TestReenterLock implements Runnable {

    public static ReentrantLock lock = new ReentrantLock();

    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestReenterLock instance = new TestReenterLock();
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

代碼例子中纤虽,操作臨界資源前乳绕,通過lock.lock()方法獲取鎖,操作完成后逼纸,通過unlock方法釋放鎖洋措。
接下來看lock方法的實現(xiàn)。

lock方法

ReentrantLock中的lock方法很簡單杰刽。

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

其委托給sync的lock方法菠发。
由上圖可以看到,Sync是一個繼承至AQS的類abstract static class Sync extends AbstractQueuedSynchronizer贺嫂,其為一個抽象的類滓鸠。有兩個子類,F(xiàn)airSync和NonfairSync第喳,即公平鎖和非公平鎖糜俗。
首先看非公平鎖,也是默認(rèn)的實現(xiàn)曲饱。

  final void lock() {
            //嘗試獲取鎖悠抹,通過修改AQS的狀態(tài)
            if (compareAndSetState(0, 1))
            //獲取成功,將當(dāng)前的線程置為獨占的
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

compareAndSetState為AQS里的方法扩淀,源碼實現(xiàn)如下:

protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

這里用了unsafe的相關(guān)操作楔敌。compareAndSwapInt有四個參數(shù),分別是需要修改的對象驻谆,對象的內(nèi)存地址卵凑,期望的原始值和更新后的值。
回到lock方法胜臊,當(dāng)其沒有獲取到資源時勺卢,則調(diào)用acquire方法:

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

上面的代碼意思是再一次嘗試獲取資源,若沒有獲取到象对,則嘗試加入隊列中值漫。
tryAcquire代碼如下:

 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
final boolean nonfairTryAcquire(int acquires) {
          //獲取當(dāng)前線程
            final Thread current = Thread.currentThread();
          //獲取當(dāng)前鎖狀態(tài)
            int c = getState();
          //當(dāng)前狀態(tài)為0,沒有線程持有鎖织盼,可以獲取鎖了
            if (c == 0) {
              // 修改狀態(tài),獲取資源
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
          //若已有線程持有鎖酱塔,則判斷是否是當(dāng)前線程持有的沥邻,若是,則修改狀態(tài)羊娃,在原有狀態(tài)上+1
          //該處體現(xiàn)了重入鎖的概念了唐全。
            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;
        }

上面的一段代碼體現(xiàn)了重入鎖重入的概念。即如果發(fā)現(xiàn)是當(dāng)前線程持有了鎖,則線程再一次獲取鎖的時候邮利,再狀態(tài)上加一弥雹,釋放的時候?qū)tate減1,所以延届,當(dāng)對一個線程多次獲取鎖時剪勿,需要對應(yīng)次數(shù)的unlock操作,如若不然方庭,可能會使其他線程一直無法獲取鎖厕吉。如下:

lock.lock();
lock.lock();
try{
業(yè)務(wù)處理代碼
}finally{
 lock.unlock();
 lock.unlock();
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))源碼如下:
addWaiter如下:

 private Node addWaiter(Node mode) {
    //創(chuàng)建一個node
        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) {
            //將當(dāng)前的node的前驅(qū)設(shè)置為隊列的尾部
            node.prev = pred;
            //設(shè)置tail指向當(dāng)前node
            if (compareAndSetTail(pred, node)) {
              //設(shè)置原tail指向的節(jié)點的next指向當(dāng)前node,形成一個雙向鏈表械念。
                pred.next = node;
                //返回當(dāng)前節(jié)點
                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;
                }
            }
        }
    }

將自己的node放到等待隊列尾部头朱。
acquireQueued的代碼如下:

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
              //獲取node的前驅(qū)節(jié)點
                final Node p = node.predecessor();
              //若前驅(qū)節(jié)點是隊列的頭節(jié)點,且能獲取到資源龄减,則將當(dāng)前節(jié)點設(shè)置為頭節(jié)點项钮,并返回,退出循環(huán)希停。
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
              //設(shè)置當(dāng)前節(jié)點的等待狀態(tài)烁巫,并調(diào)用LockSupport.park(this)方法,阻塞當(dāng)前線程脖苏。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

以上是非公平鎖的代碼程拭,非公平體現(xiàn)在何處呢?
非公平體現(xiàn)在棍潘,當(dāng)前線程執(zhí)行完之后恃鞋,喚醒隊列頭的等待隊列時,如果有新線程來了亦歉,正好獲取到資源了恤浪,則會把該資源放到隊頭,進行執(zhí)行肴楷,而不是遵循著先到先執(zhí)行的原則水由。公平鎖就是按照隊列頭開始,一個節(jié)點一個節(jié)點的往下執(zhí)行赛蔫,先到先執(zhí)行的意思在里邊砂客。

公平鎖對應(yīng)的代碼如下:

 final void lock() {
            acquire(1);
        }

相對于非公平鎖來說,少了獲取資源的判斷呵恢,這樣就不會有線程插隊鞠值,而是老老實實的喚醒隊頭的線程。

unlock方法

unlock也是委托給sync的unlock方法渗钉。unlock沒有公不公平這一說彤恶。
unlock代碼如下:

 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方法是試圖釋放鎖钞钙。代碼如下:

  protected final boolean tryRelease(int releases) {
          //同一線程多次獲取鎖,在釋放的時候声离,需要多次unlock芒炼,體現(xiàn)在這個地方
            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;
        }

Node的waitStatus 有如下狀態(tài)值:

  /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;
private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

以上代碼大意就是從隊尾開始向前遍歷,找到離隊頭最近的一個沒有被cannel的線程术徊,調(diào)用unpark方法喚醒該等待隊列本刽。

到此,ReentrantLock差不多讀完了弧关。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盅安,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子世囊,更是在濱河造成了極大的恐慌别瞭,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件株憾,死亡現(xiàn)場離奇詭異蝙寨,居然都是意外死亡,警方通過查閱死者的電腦和手機嗤瞎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門墙歪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贝奇,你說我怎么就攤上這事虹菲。” “怎么了掉瞳?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵毕源,是天一觀的道長。 經(jīng)常有香客問我陕习,道長霎褐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任该镣,我火速辦了婚禮冻璃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘损合。我一直安慰自己省艳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布嫁审。 她就那樣靜靜地躺著跋炕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪土居。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音擦耀,去河邊找鬼棉圈。 笑死,一個胖子當(dāng)著我的面吹牛眷蜓,可吹牛的內(nèi)容都是我干的分瘾。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼吁系,長吁一口氣:“原來是場噩夢啊……” “哼德召!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汽纤,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤上岗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蕴坪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肴掷,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年背传,在試婚紗的時候發(fā)現(xiàn)自己被綠了呆瞻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡径玖,死狀恐怖痴脾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梳星,我是刑警寧澤赞赖,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站丰泊,受9級特大地震影響薯定,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞳购,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一话侄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧学赛,春花似錦年堆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绢掰,卻和暖如春痒蓬,著一層夾襖步出監(jiān)牢的瞬間童擎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工攻晒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留顾复,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓鲁捏,卻偏偏與公主長得像芯砸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子给梅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

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

  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-03】 更新日志 前言 在java中假丧,鎖是實現(xiàn)并發(fā)的關(guān)鍵組件,多個...
    一字馬胡閱讀 44,146評論 1 32
  • ReentrantLock 介紹 一個可重入的互斥鎖动羽,它具有與使用{synchronized}方法和語句訪問的隱式...
    tomas家的小撥浪鼓閱讀 4,045評論 1 4
  • 前言 上一篇文章《基于CAS操作的Java非阻塞同步機制》 分析了非同步阻塞機制的實現(xiàn)原理包帚,本篇將分析一種以非同步...
    Mars_M閱讀 4,798評論 5 9
  • 最近看到一些點擊量爆棚的公眾號文章羽德,話題類似:有多少人努力了几莽,注定要優(yōu)秀而貧窮的活著;階級固化的年代宅静,你拿什么去堅...
    丟銩小隊長閱讀 409評論 0 0
  • 本文由幣乎(bihu.com)優(yōu)質(zhì)內(nèi)容計劃支持 USDT是錨定美元發(fā)布的章蚣,所以其幣價相對比較穩(wěn)定。單這點來說肯定非...
    fliu_00閱讀 20,277評論 0 2