AQS之ReentrantLock源碼解析

概述

在編碼中常使用ReentrantLock時候启上,它可以實現(xiàn)線程在獲取鎖時候公平與非公平。所謂公平在排隊者挨個獲取鎖捏萍,非公平排隊者第一個可能和插隊者爭搶鎖。我們想來上一個類圖了解他們之間關(guān)系旷余。


關(guān)系圖

源碼分析

我們先從ReentrantLock構(gòu)造方法開始分析。

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

從代碼可以看出扁达,默認采用非公平鎖正卧。接下來了解,公平鎖和非公平鎖他們代碼具體差異在哪里跪解。

    //公平鎖
    static final class FairSync extends Sync {
 
        final void lock() {
            //1參數(shù)為1 表示要去獲得鎖
            acquire(1);
        }
  
    //非公平鎖
    static final class NonfairSync extends Sync {
        final void lock() {
            //2 通過CAS直接加鎖炉旷,如果鎖未被他人使用,
            if (compareAndSetState(0, 1))
                //3設(shè)置當前執(zhí)行的線程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    }
  • 注釋1叉讥,公平鎖窘行,直接獨占方式獲取鎖。
  • 注釋2图仓,非公平鎖罐盔,通過CAS直接加鎖,如果鎖未被他人使用救崔,修改成功獲得鎖惶看。如果沒有獲取到鎖然后調(diào)用 acquire(1)方法。
  • 注釋3六孵,設(shè)置當前執(zhí)行的線程纬黎。
    我們繼續(xù)看上面類是如何嘗試獲取鎖的。
    //公平鎖
    static final class FairSync extends Sync {

         protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //1  此方法保證獲得鎖根據(jù)隊列來 先來先獲得鎖
                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;
        }
    }


    //非公平鎖
    static final class NonfairSync extends Sync {
      //2 嘗試在去獲得鎖
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

   abstract static class Sync extends AbstractQueuedSynchronizer {
         // 嘗試獲取鎖
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //3 嘗試獲取鎖劫窒,如果鎖未被使用本今。先進入的線程
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //4 如果鎖被自己占用,自己獲取鎖可重入鎖
            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;
        }
  }
//5 判斷頭結(jié)點是否是當前線程
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer{
    public final boolean hasQueuedPredecessors() {
        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());
    }
}

  • 注釋1主巍,公平鎖冠息,hasQueuedPredecessors方法判斷頭結(jié)點(AQS后面講,線程轉(zhuǎn)換成雙向鏈表才存儲)是否是當前線程煤禽。如果是(按照鏈表順序執(zhí)行)當前線程铐达,直接嘗試獲取鎖。
  • 注釋2檬果,非公平鎖瓮孙,嘗試獲取鎖
  • 注釋3,發(fā)起線程嘗試獲取鎖选脊,如果鎖未被使用則獲取到搜杭抠,這里和鏈表的head存在競爭關(guān)系,鎖被爭搶恳啥。
  • 注釋4偏灿,如果鎖被自己占用,自己獲取鎖可重入鎖钝的。
  • 注釋5翁垂,判斷頭結(jié)點是否是當前線程铆遭。

接下來我們來分析AQS(AbstractQueuedSynchronizer)源碼,不管是公平鎖還是非公平鎖沿猜,以下實現(xiàn)機制都一樣枚荣。

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

嘗試獲取鎖,如果沒有獲取到鎖啼肩,把當前線程封裝成Node節(jié)點橄妆,存入鏈表中。

 static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;
        /** 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;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
        
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

把線程封裝成Node祈坠,且有自己的狀態(tài)waitStatus害碾。

//1 為獲取鎖的線程,添加等待節(jié)點
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // 2 tail 尾結(jié)點
        Node pred = tail;
        // 尾結(jié)點不為空
        if (pred != null) {
            //3 在尾結(jié)點添加新節(jié)點
            node.prev = pred;
            //4 修改尾結(jié)點的內(nèi)存地址內(nèi)存赦拘,tail = node
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
  // 創(chuàng)建tail節(jié)點內(nèi)容慌随,第一個節(jié)點為哨兵節(jié)點,第二個節(jié)點為node
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //5 尾結(jié)點為空另绩,創(chuàng)建哨兵節(jié)點(Node都是一個空對象)
            if (t == null) { // Must initialize
                //6 tail head 都為哨兵節(jié)點
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //7 為node 指定 prev節(jié)點儒陨,首先初始化哨兵節(jié)點花嘶,然后把哨兵節(jié)點作為node的prev笋籽。
                node.prev = t;
                //8 尾結(jié)點變化成 node
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
  • 注釋1,為獲取鎖的線程椭员,添加等待節(jié)點车海。
  • 注釋2,拿到尾結(jié)點隘击。
  • 注釋3侍芝,如果pre節(jié)點不為空,添加新節(jié)點到尾結(jié)點處埋同。
  • 注釋4州叠,修改尾結(jié)點的內(nèi)存地址內(nèi)存的值,tail = node凶赁。
  • 注釋5咧栗,尾結(jié)點為空,創(chuàng)建哨兵節(jié)點(Node都是一個空對象)虱肄。
  • 注釋6致板,給tail和head 賦值哨兵節(jié)點。
  • 注釋7咏窿,為node 指定 prev節(jié)點斟或,首先初始化哨兵節(jié)點,然后把哨兵節(jié)點作為node的prev集嵌。
  • 注釋8萝挤,尾結(jié)點變化成 node御毅。
    總結(jié),上面兩個方法主要做的事情是怜珍,創(chuàng)建哨兵節(jié)點亚享,下一個節(jié)點為node。
 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //1 獲得node的上一個節(jié)點
                final Node p = node.predecessor();
                //2 p節(jié)點是頭結(jié)點绘面,通過調(diào)用tryAcquire方法獲得鎖
                if (p == head && tryAcquire(arg)) {
                    //3 把node節(jié)點變成哨兵節(jié)點
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //4 Node狀態(tài)變成等待欺税,刪除線程取消的節(jié)點。parkAndCheckInterrupt等待獲取鎖
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
// 進入阻塞狀態(tài)
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //5 等待獲取鎖狀態(tài)
        if (ws == Node.SIGNAL)
            return true;
        // 6 線程被取消
        if (ws > 0) {
            do {
                //7 刪除被取消的線程節(jié)點
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • 注釋1揭璃,獲得node(新線程)的上一個節(jié)點晚凿。
  • 注釋2,p節(jié)點是頭結(jié)點瘦馍,通過調(diào)用tryAcquire方法獲得鎖歼秽。
  • 注釋3,把node節(jié)點變成哨兵節(jié)點情组。
  • 注釋4燥筷,Node狀態(tài)變成等待,刪除線程取消的節(jié)點院崇。parkAndCheckInterrupt等待獲取鎖肆氓。
  • 注釋5,等待獲取鎖狀態(tài)底瓣。
  • 注釋6谢揪,被標識為取消的節(jié)點。
  • 注釋7捐凭,刪除被取消的線程節(jié)點拨扶。
    我們了解到通過AQS給需要加鎖的線程,封裝成Node雙向鏈表茁肠。接下來我們看看如何喚醒阻塞的線程患民。
    //ReentrantLock.java
    // 釋放鎖
    public void unlock() {
        sync.release(1);
    }

//AbstractQueuedSynchronizer.java
public final boolean release(int arg) {
        if (tryRelease(arg)) {
          //1 獲取得到頭結(jié)點
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
 private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //2 拿到頭結(jié)點下一個節(jié)點,都是等待獲取鎖的節(jié)點
        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;
        }
        // 給節(jié)點s垦梆,發(fā)一張允許票
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • 注釋1匹颤,獲取得到頭結(jié)點。
  • 注釋2奶赔,拿到頭結(jié)點下一個節(jié)點惋嚎,都是等待獲取鎖的節(jié)點。
    以上是所有線程使用ReentrantLock原理站刑,如有描述錯誤地方請大家多多指正另伍。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子摆尝,更是在濱河造成了極大的恐慌温艇,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堕汞,死亡現(xiàn)場離奇詭異勺爱,居然都是意外死亡,警方通過查閱死者的電腦和手機讯检,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門琐鲁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人人灼,你說我怎么就攤上這事围段。” “怎么了投放?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵奈泪,是天一觀的道長。 經(jīng)常有香客問我灸芳,道長涝桅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任烙样,我火速辦了婚禮冯遂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘误阻。我一直安慰自己债蜜,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布究反。 她就那樣靜靜地躺著,像睡著了一般儒洛。 火紅的嫁衣襯著肌膚如雪精耐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天琅锻,我揣著相機與錄音卦停,去河邊找鬼。 笑死恼蓬,一個胖子當著我的面吹牛惊完,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播处硬,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼小槐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凿跳,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤件豌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后控嗜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茧彤,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年疆栏,在試婚紗的時候發(fā)現(xiàn)自己被綠了曾掂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡壁顶,死狀恐怖遭殉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情博助,我是刑警寧澤险污,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站富岳,受9級特大地震影響蛔糯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窖式,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一蚁飒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萝喘,春花似錦淮逻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至启妹,卻和暖如春筛严,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饶米。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工桨啃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人檬输。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓照瘾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丧慈。 傳聞我的和親對象是個殘疾皇子析命,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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