ReentrantLock重入鎖和 AQS同步器源碼解析

ReentrantLock重入鎖和 AQS同步器源碼解析

  • AQS就是AbstractQueuedSynchronizer撵割,是一個(gè)java的同步器,用來管理多線程對(duì)共享資源的爭搶包颁,以及對(duì)線程的排隊(duì)和喚醒瞻想。
  • 同步器支持獨(dú)占式的獲取資源也支持共享式的獲取資源
  • 獨(dú)占式資源需要實(shí)現(xiàn)tryAcquire和tryRelease方法
  • 共享式資源需要實(shí)現(xiàn)tryAcquireShared和tryReleaseShared
  • 同步器內(nèi)部維護(hù)了一個(gè)雙向鏈表,用來保存爭搶鎖的隊(duì)列
  • 同步器主要利用cas實(shí)現(xiàn)線程安全
  • java api的ReentrantLock(可重入鎖)和CountDownLatch都利用了同步器來實(shí)現(xiàn)
  • ReentrantLock是利用獨(dú)占式資源實(shí)現(xiàn)
  • CountDownLatch是利用共享式資源實(shí)現(xiàn)

ReentrantLock 可重入鎖分析

  • 可重入鎖分為公平鎖和非公平鎖

  • 公平鎖是所有爭搶鎖的線程會(huì)形成一個(gè)隊(duì)列娩嚼,以先來先得順序獲取鎖

  • 非公平鎖是每個(gè)新到的線程都會(huì)去嘗試獲取一次鎖蘑险,獲取不到才會(huì)排到隊(duì)列里

  • 非公平鎖NonfairSync分析

    • Sync繼承自AQS,NonfairSync 繼承自 Sync

    • NonfairSync實(shí)現(xiàn)了lock方法以及 tryAcquire方法,同時(shí)Sync里有一個(gè)nonfairTryAcquire方法用于非公平鎖獲取鎖

    • 接下來開始看非公平鎖的源碼解析岳悟,首先是lock方法

    • final void lock() {
          if (compareAndSetState(0, 1))
              setExclusiveOwnerThread(Thread.currentThread());
          else
              acquire(1);
      }
      // lock方法是加鎖方法佃迄,該方法會(huì)首先用cas嘗試獲得一次鎖,如果成功設(shè)置獲得鎖線程會(huì)當(dāng)前線程
      // 否則執(zhí)行acquire加鎖方法贵少,該方法是AQS的方法
      
    • public final void acquire(int arg) {
          if (!tryAcquire(arg) &&
              acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
              selfInterrupt();
      }
      // 該方法首先執(zhí)行左邊tryAcquire嘗試獲得鎖呵俏,該方法由子類NonfairSync實(shí)現(xiàn),
      // 如果獲得鎖則&&后面不執(zhí)行滔灶,如果不能獲得鎖普碎,則執(zhí)行acquireQueued方法將鎖加入隊(duì)列。
      // 而tryAcquire方法在子類實(shí)際是調(diào)用Sync的nonfairTryAcquire方法
      
    • 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;
      }
      // 該方法會(huì)獲取當(dāng)前資源狀態(tài)录平,state麻车,0代表無人持有鎖,大于0則代表由線程持有鎖
      // 如果是0斗这,則嘗試cas獲取动猬,如果當(dāng)前獲取鎖的線程是當(dāng)前線程,則將鎖狀態(tài)加1涝影,代表重入一次
      // 否則獲取不到鎖返回false
      
    • 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;
      }
      // 該方法會(huì)將當(dāng)前獲取鎖的線程封裝成Node鏈入鏈表尾部枣察,
      // 如果當(dāng)前鏈表是空的話,會(huì)執(zhí)行enq初始化一個(gè)鏈表
      
    • 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;
                  }
              }
          }
      }
      // 這里首先會(huì)開啟一個(gè)循環(huán)自旋燃逻,然后判斷鏈表是否是空序目,
      // 如果是空會(huì)初始化一個(gè)空節(jié)點(diǎn)作為鏈表的頭,然后在將當(dāng)前節(jié)點(diǎn)鏈入節(jié)點(diǎn)的尾部
      // 那么為什么要把當(dāng)前節(jié)點(diǎn)鏈表鏈入空節(jié)點(diǎn)后伯襟,而不是head節(jié)點(diǎn)呢猿涨,
      // 這是因?yàn)閔ead節(jié)點(diǎn)始終是作為當(dāng)前持有線程的節(jié)點(diǎn),
      // 但是當(dāng)前持有線程的節(jié)點(diǎn)可能會(huì)因?yàn)橹苯訝帗尩芥i而沒有進(jìn)入鏈表姆怪,比如當(dāng)前這個(gè)情況叛赚,
      // 該線程沒獲得鎖,但是鏈表空的稽揭,所以要把他自己放在第二個(gè)位置
      
    • 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);
          }
      }
      // 節(jié)點(diǎn)鏈入鏈表后俺附,會(huì)開啟自旋為自己獲取鎖,
      // 首先會(huì)判斷當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)是不是head節(jié)點(diǎn)溪掀,
      // 如果是說明當(dāng)前節(jié)點(diǎn)可以嘗試獲取一次鎖事镣,如果獲取到則返回,
      // 獲取不到則會(huì)shouldParkAfterFailedAcquire揪胃,將前置節(jié)點(diǎn)設(shè)置為Signal璃哟,
      // 告訴他釋放鎖后進(jìn)行通知氛琢,然后會(huì)執(zhí)行parkAndCheckInterrupt,
      // 該方法內(nèi)部會(huì)執(zhí)行LockSupport.park(this);随闪,該行代碼會(huì)將當(dāng)前線程阻塞阳似,直到被unPark喚醒
      // 當(dāng)然 可能線程可能被interrupted而結(jié)束
      
    • 到這里加鎖方法就結(jié)束了,接下來分析一下鎖的釋放铐伴,釋放鎖的方法

    • public final boolean release(int arg) {
          if (tryRelease(arg)) {
              Node h = head;
              if (h != null && h.waitStatus != 0)
                  unparkSuccessor(h);
              return true;
          }
          return false;
      }
      // 釋放鎖的方法是由重入鎖的unLock方法調(diào)用AQS的release方法實(shí)現(xiàn)
      // 該方法首先會(huì)執(zhí)行tryRelease 進(jìn)行鎖的釋放撮奏,該方法是由Sync實(shí)現(xiàn),
      // 釋放后會(huì)判斷鏈表節(jié)點(diǎn)是否是空当宴,并且首節(jié)點(diǎn)是否不等于0(Signal狀態(tài)是-1)挽荡,
      // 然后執(zhí)行喚醒下個(gè)線程的操作unparkSuccessor
      
    • 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;
      }
      // 先看Sync的tryRelease方法,先判斷當(dāng)前線程是否是獲取到鎖的線程即供,如果不是則拋出異常
      // 接下來當(dāng)鎖狀態(tài)減1(參數(shù)就是1)后是否等于0,等于0代表鎖已經(jīng)完全釋放于微,可以返回true逗嫡,否則返回false
      // 只有返回true AQS才會(huì)去處理喚等待線程
      
    • private void unparkSuccessor(Node node) {
          int ws = node.waitStatus;
          if (ws < 0)
              compareAndSetWaitStatus(node, ws, 0);
          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);
      }
      // 在這里,如果狀態(tài)Signal(小于0)代表是需要通知株依,然后首先將首節(jié)點(diǎn)設(shè)置為0
      // 然后獲取到下一個(gè)節(jié)點(diǎn)驱证,如果下一個(gè)節(jié)點(diǎn)是null或者下一個(gè)節(jié)點(diǎn)的狀態(tài)是廢棄(狀態(tài)大于0代表是廢棄的節(jié)點(diǎn)),
      // 則從尾部開始逐漸向前著恋腕,直到找到一個(gè)可用節(jié)點(diǎn)(小于等于0的節(jié)點(diǎn)),
      // 這里為什么要從尾部向前找呢抹锄?我也不知道。荠藤。伙单。。
      // 執(zhí)行unPark喚醒找到的節(jié)點(diǎn)對(duì)應(yīng)的線程
      
    • 釋放鎖的流程也就這么結(jié)束了哈肖,這就是非公平鎖的流程吻育,那么公平鎖的流程是什么樣的呢?剛才我們提到了淤井,非公平鎖的不公平點(diǎn)在于每個(gè)新來爭搶的線程都會(huì)去嘗試獲取一次鎖布疼,也就是插隊(duì)加塞一次,獲取不到才會(huì)去乖乖排隊(duì)币狠,那么公平鎖在獲取鎖的時(shí)候一定就是要判斷有沒有人在等待游两,有的話就去排隊(duì),沒有才去獲取鎖漩绵,接下來看看源碼

    • 在FairSync下的tryAcquire方法

    •     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;
          }
      }
      // 所以我們看到贱案,線程在鎖資源沒有人持有的情況下,會(huì)首先執(zhí)行hasQueuedPredecessors判斷是否有線程在等待渐行。
      // 如果沒人等待才會(huì)執(zhí)行cas獲取鎖轰坊。
      // 而如果持有鎖的是當(dāng)前線程會(huì)將鎖資源加1铸董,這和非公平鎖一致。
      //最后如果因?yàn)橛腥说却龥]獲取到鎖肴沫,AQS的acquire方法會(huì)執(zhí)行acquireQueued方法將節(jié)點(diǎn)鏈入鏈表
      // 而釋放鎖對(duì)于非公平鎖和公平鎖來說都是一致的粟害。
      // 這就是非公平鎖和公平鎖不一致的地方
      
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市颤芬,隨后出現(xiàn)的幾起案子悲幅,更是在濱河造成了極大的恐慌,老刑警劉巖站蝠,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汰具,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡菱魔,警方通過查閱死者的電腦和手機(jī)留荔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澜倦,“玉大人聚蝶,你說我怎么就攤上這事≡逯危” “怎么了碘勉?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桩卵。 經(jīng)常有香客問我验靡,道長,這世上最難降的妖魔是什么雏节? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任胜嗓,我火速辦了婚禮,結(jié)果婚禮上矾屯,老公的妹妹穿的比我還像新娘兼蕊。我一直安慰自己,他們只是感情好件蚕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布孙技。 她就那樣靜靜地躺著,像睡著了一般排作。 火紅的嫁衣襯著肌膚如雪牵啦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天妄痪,我揣著相機(jī)與錄音哈雏,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛裳瘪,可吹牛的內(nèi)容都是我干的土浸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼彭羹,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼黄伊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起派殷,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤还最,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后毡惜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拓轻,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年经伙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扶叉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帕膜,死狀恐怖辜梳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泳叠,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布茶宵,位于F島的核電站危纫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏乌庶。R本人自食惡果不足惜种蝶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞒大。 院中可真熱鬧螃征,春花似錦、人聲如沸透敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酗电。三九已至魄藕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撵术,已是汗流浹背背率。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寝姿。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓交排,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饵筑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子埃篓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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