激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

香港云服务器
服務器之家 - 編程語言 - Java教程 - 基于ReentrantLock的實現原理講解

基于ReentrantLock的實現原理講解

2022-01-21 11:47一擼向北 Java教程

這篇文章主要介紹了ReentrantLock的實現原理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

java.util.concurrent包中的工具實現核心都是AQS,了解ReentrantLock的實現原理,需要先分析AQS以及AQS與ReentrantLock的關系。

這篇文章中分析了ReentrantLock#lock與ReentrantLock#unlock的實現,對于Condition的實現分析,另外文章再講,基本上大同小異。

 

ReentrantLock實現核心CAQS(AbstractQueuedSynchronizer)

AQS,隊列同步器,在juc包中的工具類都是依賴于AQS來實現同步控制,看一張AQS的結構圖。

基于ReentrantLock的實現原理講解

同步控制中主要使用到的信息如上圖所示。AQS可以被當做是一個同步監視器的實現,并且具有排隊功能。當線程嘗試獲取AQS的鎖時,如果AQS已經被別的線程獲取鎖,那么將會新建一個Node節點,并且加入到AQS的等待隊列中,這個隊列也由AQS本身自己維護。當鎖被釋放時,喚醒下一個節點嘗試獲取鎖。

  • 變量exclusiveOwnerThread在互斥模式下,表示當前持有鎖的線程。
  • 變量tail指向等待獲取AQS的鎖的節點隊列的最后一個
  • 變量head指向隊列中head節點,head節點信息為空,不表示任何正在等待的線程。
  • 變量state表示AQS同步器的狀態,在不同情況下含義可能不太一樣,例如以下幾種
    • 在ReentrantLock中,表示AQS的鎖是否已經被占用獲取,0:沒有,>=1:已被獲取,當大于1時表示被同一線程多次重入鎖。
    • 在CountDownLatch中,表示計數還剩的次數,當到達0時,喚醒等待線程。
    • 在Semaphore中,表示AQS還可以被獲取鎖的次數,獲取一次就減1,當到達0時,嘗試獲取的線程將會阻塞。

Node結構

Node節點是AQS管理的等待隊列的節點元素,除了head節點之外,其他一個節點就代表一個正在等待線程的隊列。Node一般的重要參數有幾個。

  • prev 前置節點
  • next后置節點
  • thread 代表的線程
  • waitStatus節點的等待狀態
    • 1表示節點已經取消,也就是線程可能已經中斷,不需要再等待獲取鎖了,在后續代碼中會處理跳過waitStatus等于1的節點
    • -1表示當前節點的后置節點代表的線程需要被掛起
    • -2表示當前線程正在等待的是Condition鎖

 

ReentrantLock實現分析

二者關聯

ReentrantLock實現核心是基于AQS,看下面一張圖,分析AQS與ReentrantLock的關系。

基于ReentrantLock的實現原理講解

從圖中可以看出,ReentrantLock里面有最終兩個內部類,FairSync和NonfairSync通過繼承AbstractQueuedSynchronizer的功能,來實現兩種同步互斥方案:公平鎖和非公平鎖。

在ReentrantLock中最終lock和unlock操作,都由FairSync和NonfairSync實際完成。

NonfairSync分析

下面看一個最簡單利用ReentrantLock實現互斥的例子。

      ReentrantLock lock = new ReentrantLock();
		//嘗試獲取鎖
      lock.lock();
      //獲得鎖后執行邏輯......
      //......
		//解鎖
      lock.unlock();

在ReentrantLock的默認無參構造方法中,創建的是非公平鎖

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

下面分析lock.lock();這句代碼是如何實現同步互斥的。

NonfairSync#lock

點開lock.lock();源碼,可以看到最終實際調用的是NonfairSync#lock,這是分析的入口。

NonfairSync#lock源碼如下。

final void lock() {
  if (compareAndSetState(0, 1))//【step1】
      setExclusiveOwnerThread(Thread.currentThread());//【step2】
  else
      acquire(1);//【step3】
}

【step1】上面有提到,NonfairSync繼承自AbstractQueuedSynchronizer,NonfairSync就是一個AQS,因此在步驟【1】其實就是利用CAS(一個原子性的比較并設置操作)嘗試設置AQS的state為1。如果設置成功,表示獲取鎖成功;如果設置失敗,表示state之前已經是>=1,已經被別的線程占用了AQS的鎖,所示無法設置state為1,稍后會把線程加入到等待隊列。

**非公平鎖與公平鎖:**對于NonfairSync非公平鎖來說,線程只要執行lock請求,就會立馬嘗試獲取鎖,不會管AQS當前管理的等待隊列中有沒有正在等待的線程,這種操作是不公平的,沒有先來后到;而稍后介紹的FairSync公平鎖,則會在lock請求進行時,先判斷AQS管理的等待隊列中是否已經有正在等待的線程,有的話就是不嘗試獲取鎖,直接進入等待隊列,保證了公平性。

這一步需要熟悉的是CAS操作,分析一下compareAndSetState源碼,如下。這一步利用unsafe包的cas操作,unsafe包類似一種java留給開發者的后門,可以用來直接操作內存數據,并且保證這個操作的原子性。在下面的代碼中,stateOffset表示state比變量的內存偏移地址,用來尋找state變量內存位置。整個cas操作就是找到內存中當前的state變量值,并且與expect期待值比較,如果跟期待值相同,那么表示這個值是可以修改的,此時就會對state值進行更新;如果與期待值不一樣,那么將不能進行更新操作。unsafe保證了比較與設置值的過程是原子性的,在這一步不會出現線程安全問題。

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

【step2】操作是在設置state成功之后,表示當前線程獲取AQS鎖成功,需要設置AQS中的變量exclusiveOwnerThread為當前持有鎖的線程,做保存記錄。

【step3】當嘗試獲取鎖失敗的時候,就需要進行步驟3,執行acquire,進行再次嘗試或者線程進入等待隊列。

AbstractQueuedSynchronizer#acquire

當第一次嘗試獲取鎖失敗之后,執行【step3】acquire方法,這個方法可以講一個嘗試獲取鎖失敗的線程放入AQS管理的等待隊列進行等待,并且將線程掛起。實現邏輯在AbstractQueuedSynchronizer實現,NonfairSync通過繼承AbstractQueuedSynchronizer獲得等待隊列管理的功能。

下面看源碼。

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

NonfairSync#tryAcquireC鎖重入實現

首先,執行tryAcquire再次嘗試一次獲取lock,tryAcquire是由子類實現,也就是這里調用NonfairSync#tryAcquire方法,跟蹤調用,最終執行代碼如下。

final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
      //如果此時state已經變為1,再次嘗試一次獲取鎖
      if (compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
      }
  }
  else if (current == getExclusiveOwnerThread()) {
      //否則判斷當前持有AQS的鎖的線程是不是當前請求獲取鎖的線程,是的話就進行鎖重入。
      int nextc = c + acquires;
      if (nextc < 0) // overflow
          throw new Error("Maximum lock count exceeded");
      setState(nextc);
      return true;
  }
  return false;
}

對于NonfairSync#tryAcquire的實現,首先是重新嘗試一次獲取鎖,如果還是獲取不到,就嘗試判斷當前的是不是屬于重入鎖的情形。

對于重入鎖的情形,就需要對state進行累加1,意思就是重入一次就對state加1。這樣子,在解鎖的時候,每次unlock就對state減一,等到state的值為0的時候,才能喚醒下一個等待線程。

如果獲取成功,返回true;否則返回false,繼續執行下一步acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),添加一個Node節點進入AQS管理的等待隊列。

AbstractQueuedSynchronizer#addWaiterC添加Node到等待隊列

這個方法屬于隊列管理,也是由AbstractQueuedSynchronizer默認實現,NonfairSync繼承獲得。

查看addWaiter源碼實現。

private Node addWaiter(Node mode) {
  //新建一個Node節點,mode傳入表示當前線程之間競爭是互斥模式
  Node node = new Node(Thread.currentThread(), mode);
  // Try the fast path of enq; backup to full enq on failure
  //嘗試添加當前新建Node到鏈表隊列末尾
  Node pred = tail;
  if (pred != null) {
      node.prev = pred;
      //利用cas設置尾指針指向的節點為當前線新建節點
      if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
      }
  }
  //當前是空的沒有任何正在等待的線程Node的時候,執行enq(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 {
          //跟之前一樣,利用cas這只當前新建節點為尾節點
          node.prev = t;
          if (compareAndSetTail(t, node)) {
              t.next = node;
              return t;
          }
      }
  }
}

以上操作,完成將一個節點加入隊列操作。加入完成之后,返回這個新加入的節點Node給acquireQueued方法繼續執行。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))C鎖競爭優化

上面既然都完成了等待節點如隊列的操作,為什么不直接掛起線程進入等待呢?因此這里的設計者做了一個優化操作。acquireQueued方法其實就是為了減少線程掛起、喚醒次數而作的優化操作。

下面看看acquireQueued的代碼實現。

final boolean acquireQueued(final Node node, int arg) {
  boolean failed = true;
  try {
      boolean interrupted = false;
      for (;;) {
          //獲取當前競爭鎖的線程Node的前置節點
          final Node p = node.predecessor();
          //【step1】
          if (p == head && tryAcquire(arg)) {
              setHead(node);
              p.next = null; // help GC
              failed = false;
              return interrupted;
          }
          //【step2】
          if (shouldParkAfterFailedAcquire(p, node) &&
              parkAndCheckInterrupt())
              interrupted = true;
      }
  } finally {
      if (failed)
          cancelAcquire(node);
  }
}

【step1】假如前置節點是head,那么表示當前線程是等待隊列中最大優先級的等待線程,可以繼續最后的嘗試獲取鎖,因為很有可能會獲取到鎖,從而避免線程掛起、喚醒,耗費資源,這里也算是最終一次嘗試獲取。

【step2】shouldParkAfterFailedAcquire(p, node)是檢查當前是否有必要掛起,前面我們說過,只有當前置節點的waitStatus是-1的時候才會掛起當前節點代表的線程。當shouldParkAfterFailedAcquire(p, node)返回true的時候,就可以執行parkAndCheckInterrupt()來掛起線程。

shouldParkAfterFailedAcquire(p, node)源碼如下。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  int ws = pred.waitStatus;
  if (ws == Node.SIGNAL)
      //前置節點是-1,返回true表示線程可掛起
      return true;
  if (ws > 0) {
      //前置節點大于0表示前置節點已經取消,那么進行跳過前置節點的操作,做鏈表的基本刪除節點操作
      do {
          node.prev = pred = pred.prev;
      } while (pred.waitStatus > 0);
      pred.next = node;
  } else {
      //如果前置節點還是0,表示前置節點Node的waitStatus是初始值,需要設置為-1,然后外層循環重新執行shouldParkAfterFailedAcquire方法,即可掛起當前線程。
      compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  }
  return false;
}
private final boolean parkAndCheckInterrupt() {
  //阻塞掛起線程,等待喚醒
  LockSupport.park(this);
  //喚醒后,重置中斷標記,線程中斷標記位為不中斷
  return Thread.interrupted();
}

FairSync分析

之前提到

**非公平鎖與公平鎖:**對于NonfairSync非公平鎖來說,線程只要執行lock請求,就會立馬嘗試獲取鎖,不會管AQS當前管理的等待隊列中有沒有正在等待的線程,這種操作是不公平的,沒有先來后到;而稍后介紹的FairSync公平鎖,則會在lock請求進行時,先判斷AQS管理的等待隊列中是否已經有正在等待的線程,有的話就是不嘗試獲取鎖,直接進入等待隊列,保證了公平性。

所以兩者的實現區別在于第一次嘗試lock的動作不一樣。

FairSync#lock

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

最終差異體現在FairSync#tryAcquire的實現(第一次嘗試獲取lock)

protected final boolean tryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
      //hasQueuedPredecessors判斷隊列是否還有別的線程在等待鎖,沒有的話就嘗試獲取lock
      //如果有別的線程在等待鎖,就不會嘗試獲取lock;下面如果也不是重入的情況的話就直接進入等待隊列
      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;
}

AbstractQueuedSynchronizer#release --AQS解鎖操作

AQS中定義了解鎖操作的模板方法,tryRelease(arg)是不同AQS子類實現,對state的多樣化操作。例如ReentrantLock中的tryRelease(arg)操作比較明顯的就是對state減一。

tryRelease(arg)返回結果表示本次操作后是否需要喚醒下一個等待節點。對于ReentrantLock就是state減一之后是否變為0了。如果需要喚醒下一個節點的線程,那么判斷一下Head有沒有下一個要喚醒的節點線程,有的話就進行喚醒操作unparkSuccessor(h);

public final boolean release(int arg) {
  if (tryRelease(arg)) {
      Node h = head;
      if (h != null && h.waitStatus != 0)
          unparkSuccessor(h);
      return true;
  }
  return false;
}

ReentrantLock.Sync#tryReleaseC解鎖實現

看一下ReentrantLock.Sync的tryRelease實現.是如何為state減一 的。。

protected final boolean tryRelease(int releases) {
  //獲取state減掉realease,對于ReentrantLock就是默認減一
  int c = getState() - releases;
  if (Thread.currentThread() != getExclusiveOwnerThread())
      throw new IllegalMonitorStateException();
  boolean free = false;
  if (c == 0) {
      //如果減到0了,那么久釋放鎖
      free = true;
      //設置持有線程為null
      setExclusiveOwnerThread(null);
  }
  //設置state為新的
  setState(c);
  return free;
}

至于這里設置state為啥不同cas操作,因為

  if (Thread.currentThread() != getExclusiveOwnerThread())
      throw new IllegalMonitorStateException();

所以永遠只有持有鎖的線程會做解鎖減一的操作,state設置是線程安全的。

 

注意一下

其實這里還沒分析Condition的實現原理,篇幅太長,下次另開文章分析。以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/qq_20597727/article/details/86263237

延伸 · 閱讀

精彩推薦
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
620
主站蜘蛛池模板: 毛片国产| 亚洲成人精品久久久 | 久久免费视频8 | 网站激情 | 久久亚洲精品久久国产一区二区 | 亚洲成人福利电影 | 99亚洲国产精品 | 福利免费在线观看 | 精品国产一区二区亚洲人成毛片 | 国产精品久久久久久久久久东京 | 91视频站 | 中文字幕一区二区三区久久 | 精品一区二区三区免费毛片 | 亚洲国产精品久久久久 | 成人视屏在线 | 久久久久久免费 | 亚洲免费资源 | 91久久久久久久一区二区 | 中文黄色一级片 | 免费a级毛片大学生免费观看 | 欧美一级做a| 亚洲最大av网站 | 国产成人高清成人av片在线看 | 日本教室三级在线看 | 毛片电影网址 | 女人裸体让男人桶全过程 | 精品人伦一区二区三区蜜桃网站 | 国产午夜精品一区二区三区免费 | 一级电影在线观看 | 欧美激情在线播放 | 欧美人与性禽动交精品 | 竹内纱里奈55在线观看 | 男男啪羞羞视频网站 | 免费亚洲视频在线观看 | 免费黄网站在线播放 | 亚洲草逼视频 | 亚洲综合视频一区 | 午夜精品久久久久久久99热浪潮 | 91网在线播放| tube69xxxxxhd | 久久电影一区二区 |