Java 并發(fā) AQS 重入鎖

實現(xiàn)重入鎖ReentrantLock鎖使用到的技術(shù)

  1. CAS 保證操作原子性
  2. AQS 帶有頭尾節(jié)點的隊列 鏈表實現(xiàn)
Node {
//Node代表了等待的線程
  Node prev 前一個Node
  Node next 后面一個Node
}

AQS{
Node head 頭節(jié)點
Node tail 尾結(jié)點
}
  1. 自旋操作

用到的一些方法的含義(公平鎖實現(xiàn)情況)(代碼為Open-JDK13中的)

  1. tryAcquire 本線程嘗試獲取鎖,獲取成功返回true
  2. hasQueuedPredecessors 判斷本線程是否需要排隊,需要排隊返回true
  3. addWaiter 往隊列中添加一個Node 即添加一個排隊的線程

源碼具體執(zhí)行流程
步驟一:

上鎖.png

步驟二:
獲取鎖.png

步驟三:
獲取鎖具體過程.png

這里開始麻煩起來了穿扳,第一步本線程第一次嘗試獲取鎖,調(diào)用 tryAcquire茫藏, 如果獲取成功,則上鎖過程結(jié)束霹琼,如果獲取失敗就執(zhí)行另外兩步刷允。
先看下tryAcquire干什么了(為公平鎖實現(xiàn)的tryAcquire)

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            獲取鎖的狀態(tài)
            int c = getState();
            如果狀態(tài)為0,說明此時鎖時空閑的
            但是注意可能有多個線程同時檢測到鎖空閑冤留,那么就會有多個線程都進(jìn)入下面的IF
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
         ... 省略一部分代碼碧囊,先不討論
            return false;
        }

如果是空閑狀態(tài)树灶,那么就要先看看是不是要排隊(因為可能有多個線程同時,非公平鎖就不用檢測要不要排隊)

    public final boolean hasQueuedPredecessors() {
        Node h, s;     h表示頭結(jié)點糯而,s表示后續(xù)節(jié)點
        如果h==null 那么就說明此此時隊列都沒創(chuàng)建 本線程是第一個線程天通,說明沒有其他線程,就**不用排隊**
        if ((h = head) != null) {
            s==null說明下一個沒有 但也可能因為并發(fā)的原因 不能保證后面也是空的
            s.waitStatus>0 說明下一個被取消了熄驼,要看后面的是什么情況
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null; // traverse in case of concurrent cancellation
                for (Node p = tail; p != h && p != null; p = p.prev) {   從后往前找
                    找到一個不是null的也不是被取消的
                    也就是找到隊列中第一個真正在等待的
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            不用排隊情況(人即線程)
                情況一:根本沒有在等待像寒,前面可能有人,也可能沒有人
                情況二:有在等待的瓜贾,但是等待的人是自己诺祸,前面肯定有人
            要排隊的情況
                有在真正等待的,并且第一個真正在等待的不是自己
              s==null 就說明沒有等待的   **不用排隊** 隊列已經(jīng)初始化過了
              s!=null就說明有等待的祭芦,
                    等待的人不是自己    **要排隊**
                    等待的人是自己   **不用排隊** 這里會比較困惑筷笨,因為這個情形的出現(xiàn)要結(jié)合后面的**acquireQueued方法來看**
                  因為如果你是第一個等待者,那么acquireQueued會再調(diào)用一次tryAcquire龟劲,而tryAcquire會再調(diào)用一次hasQueuedPredecessors胃夏,
                  那么這種情形就會出現(xiàn)了即等待的人是自己
            if (s != null && s.thread != Thread.currentThread())
                return true;
        }
        return false;
    }

這個判斷排隊比較繞。昌跌。仰禀。
然后就是回到了之前的tryAcquire了,如果不需要排隊蚕愤,那么直接用CAS操作嘗試修改鎖的狀態(tài)答恶,如果修改成功那么獲取鎖就成功了。
如果不需要帶隊,但是獲取鎖失敗了那就說明實際上還是需要排隊的萍诱,因為這種情況就說明了存在多線程競爭
如果需要排隊就要調(diào)用addWaiter往隊列中添加等待者悬嗓,即排隊,這個方法內(nèi)部就不展開了砂沛,就是入隊烫扼,它用CAS操作保證了入隊順序是線程安全的。 注意排好隊后碍庵,線程還沒有阻塞映企。
排好隊后隊列中排隊的人看看自己能不能獲取鎖,如果不能就阻塞線程静浴,直到被喚醒

    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            死循環(huán)堰氓,直到獲取鎖為止,當(dāng)然獲取失敗就會阻塞苹享,直到被喚醒双絮,然后再嘗試獲取 如此循環(huán)
            for (;;) {
                final Node p = node.predecessor();
                情形一 加入隊列后如果自己是第一個等待的那么就在試著獲取一次鎖浴麻,
                        原因是再自己第一次嘗試獲取鎖失敗加入隊列后的這段時間,擁有鎖的那個線程可能釋放了鎖
                更后面的等待者是沒有機會嘗試獲取鎖的
                情形二阻塞后被喚醒
                說明自己是第一個等待的 再嘗試獲取鎖
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
               決定是否阻塞  根據(jù)它前面的人的狀態(tài)
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();  阻塞囤攀,并且阻塞醒來也是從這里開始
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

shouldParkAfterFailedAcquire這個方法也是比較令人困惑的地方软免,也是我自己理解還不到位的地方

這個方法其實會執(zhí)行兩遍,第一遍是把前面等待者(如果有的話)的等待狀態(tài)設(shè)置好(設(shè)置成阻塞等待)
第二遍再確定自己是否需要阻塞
如果你都因為獲取鎖失敗
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      前面一個等待者的等待狀態(tài)
        int ws = pred.waitStatus;
        說明前面一個已經(jīng)在阻塞等待了焚挠,那么自然我也就要阻塞等待了
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
             你前面的等待者(等待線程)可能被取消了 找到一個沒有被取消的
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            第一次執(zhí)行 把前面的等待者狀態(tài)設(shè)置好膏萧,因為它肯定已經(jīng)阻塞等待
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }

這里說的比較亂。蝌衔。榛泛。
為什么要后面的人來設(shè)置前面的人的阻塞狀態(tài)呢,因為一個線程真正阻塞了噩斟,那么它的阻塞狀態(tài)才能設(shè)置曹锨,即先有阻塞,然后有阻塞狀態(tài)剃允,但是阻塞的線程就根本不能動了沛简,自然也不能設(shè)置阻塞狀態(tài)。
拿睡覺來打個比方硅急,你睡著還是沒睡著不能由你說了算覆享,你不可能告訴別人你睡著了,只能由別人來看营袜,只有別人才能看到你是否真正睡著了撒顿。
并且這段有解鎖過程相關(guān)性挺高的,以后有時間再說了荚板。


這篇文章主要是為了記錄自己在學(xué)習(xí)AQS時候的一些感受凤壁,也方便以后自己復(fù)習(xí)。所以用到的語言也不夠準(zhǔn)確跪另,過程不能保證正確拧抖。如果有同學(xué)看到這篇文章,一定要帶著懷疑的心態(tài)去看免绿。如果發(fā)現(xiàn)錯誤的地方希望能批評指出唧席。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嘲驾,隨后出現(xiàn)的幾起案子淌哟,更是在濱河造成了極大的恐慌,老刑警劉巖辽故,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徒仓,死亡現(xiàn)場離奇詭異,居然都是意外死亡誊垢,警方通過查閱死者的電腦和手機掉弛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門症见,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人殃饿,你說我怎么就攤上這事谋作。” “怎么了壁晒?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵瓷们,是天一觀的道長。 經(jīng)常有香客問我秒咐,道長,這世上最難降的妖魔是什么碘裕? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任携取,我火速辦了婚禮,結(jié)果婚禮上帮孔,老公的妹妹穿的比我還像新娘雷滋。我一直安慰自己,他們只是感情好文兢,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布晤斩。 她就那樣靜靜地躺著,像睡著了一般姆坚。 火紅的嫁衣襯著肌膚如雪澳泵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天兼呵,我揣著相機與錄音兔辅,去河邊找鬼。 笑死击喂,一個胖子當(dāng)著我的面吹牛维苔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播懂昂,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锈津,長吁一口氣:“原來是場噩夢啊……” “哼严嗜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤倾哺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熬苍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體管嬉,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年原探,在試婚紗的時候發(fā)現(xiàn)自己被綠了乱凿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顽素。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖徒蟆,靈堂內(nèi)的尸體忽然破棺而出胁出,到底是詐尸還是另有隱情,我是刑警寧澤段审,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布全蝶,位于F島的核電站,受9級特大地震影響寺枉,放射性物質(zhì)發(fā)生泄漏抑淫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一姥闪、第九天 我趴在偏房一處隱蔽的房頂上張望始苇。 院中可真熱鬧,春花似錦筐喳、人聲如沸催式。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荣月。三九已至,卻和暖如春梳毙,著一層夾襖步出監(jiān)牢的瞬間哺窄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工顿天, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留堂氯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓牌废,卻偏偏與公主長得像咽白,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸟缕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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