JDK成長記18: ReentrantLock (1) 通過首次加鎖初識AQS

file

上一章你應該掌握了Atomic的底層原理-CAS。接下來進入另一個重要的一個知識AQS。我們通過ReentrantLock這個類來講講AQS這個知識。

file

從上圖可以看出,ReentractLock、ReadWriteReentractLock,這些鎖API底層是基于AQS+CAS+volatile來實現(xiàn)的,一般不會直接使用,常使用的是一些并發(fā)集合API,但是它們的底層大多還是基于ReentrantLock或者AQS來實現(xiàn)的。

ReentrantLock屬于java并發(fā)包里的底層的API,專門支撐各種java并發(fā)類的底層的邏輯實現(xiàn)。

ReenranctLock的內(nèi)容比較多,計劃分6節(jié)來講。

  • 第一節(jié)講一下初識ReenranctLock加鎖的AQS底層原理

  • 第二節(jié)講一下ReenranctLock加鎖入隊的AQS底層原理

  • 第三節(jié)講一下ReenranctLock釋放鎖的底層原理

  • 第四節(jié)講一下ReenranctLock鎖的可重入、公平、非公平

  • 第五節(jié)講一下ReentrantReadWriteLock讀寫鎖的原理

  • 第六節(jié)講一下ReenranctLock中condition的應用

Hello ReentrantLock

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">Hello ReentrantLock</span></h3></div>

很多人可能沒有用過ReentrantLock,在一些并發(fā)情況下因為要保證一些原子性操作,可能也會用到。但是大多數(shù)人很少接觸高并發(fā)的場景,所以用這個類的人可能很少。

但是在一些開源項目中還是有使用到的,比如Spring Cloud的Eureka組件。有時候面試也經(jīng)??糀QS或者并發(fā)集合的問題。所以掌握ReentrantLock的原理是非常有必要的。這樣你可以駕輕就熟的理解它在開源項目的使用,更不會在面試的時候被問住。

第一點還是先來看個HelloWorld的例子。我們?yōu)榱吮WC某些操作同一時間只能有一個線程操作,會對整個操作加一個鎖。除了synchronized之外,我們還可以使用ReentrantLock。假設有一個操作是j++。

那么Hello ReentrantLock代碼如下:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {

 static int j = 1;

 public static void main(String[] args) {
  ReentrantLock reentrantLock = new ReentrantLock();
  for(int i=0;i<10;i++){
    new Thread(()->{
      reentrantLock.lock();
      try{
        System.out.println(Thread.currentThread().getName()+"-結(jié)果:"+j++);
      }catch (Exception e){

      }finally {
        reentrantLock.unlock();
      }
    }).start();
  }
 }
}

從一張圖先鳥瞰下ReentrantLock核心的3個小組件

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">從一張圖先鳥瞰下ReentrantLock核心的3個小組件</span></h3></div>

ReentrantLock核心有3個組件:state、owner、AQS(抽象隊列同步器,簡單的說就是一個等待隊列Queue)。如下圖所示:

file

這里你可以先有個概念就行,你之后會詳細的了解到這幾個組件作用的。

從JDK源碼層面找一下對應的組件

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">從JDK源碼層面找一下對應的組件</span></h3></div>

當你有了上面這張圖的印象后,我們通過Hello ReentrantLock來分析下它的組件在源碼里的體現(xiàn)。

首先還是簡單看下ReentrantLock的源碼脈絡:

file

它主要脈絡有:

1) 一些API方法

2) 3個內(nèi)部類,Sync、NonfairSync、Sync

3) 1個成員變量Sync對象

了解了源碼的大體脈絡后,接下來,分析下它的使用過程,首先肯定是創(chuàng)建ReentrantLock,讓我們來看看構(gòu)造函數(shù)做了些什么。代碼如下:

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

發(fā)現(xiàn)內(nèi)部創(chuàng)建了對象,賦值給了sync變量。創(chuàng)建了一個內(nèi)部類NonfairSync。繼續(xù)深入可以發(fā)現(xiàn)如下代碼關系:

static final class NonfairSync extends Sync {


}

abstract static class Sync extends AbstractQueuedSynchronizer {



原來sync變量創(chuàng)建的對象是NonfairSync。它的父類是Sync,而這個類的父類是一個AbstractQueuedSynchronizer。

你可以猜想下,AbstractQueuedSynchronizer這個是什么東西?從名字上看叫做抽象隊列同步器,縮寫是AQS。咿?這個就是之前提到的AQS啊。

仔細看一下這個父類的脈絡:

file

首先也是一對方法,但是變量很有意思,UnSafe類、head/tail+Node內(nèi)部類?這讓你想到了什么?

沒錯,上一節(jié)剛接觸過的Aotmic類Unsafe可以用作CAS操作的類,head/tail+Node內(nèi)部類這不是LinkedList的數(shù)據(jù)結(jié)構(gòu)么?這個就是上面提到過ReentrantLock的3個小組件之一——等待隊列Queue。

等等,還有一個int state。這個就是上面提到過ReentrantLock的3個小組件之一——state變量。你可以看到這幾個變量對應的代碼如下:

  private transient volatile Node head;

  private transient volatile Node tail;
 
  private volatile int state;

 

state和 等待隊列都看到了,owner去哪里了?原來AQS還有一個父類。叫做AbstractOwnableSynchronizer。

public abstract class AbstractQueuedSynchronizer

 extends AbstractOwnableSynchronizer

 implements java.io.Serializable {

}



public abstract class AbstractOwnableSynchronizer

 implements java.io.Serializable {

 protected AbstractOwnableSynchronizer() { }

 private transient Thread exclusiveOwnerThread;

 protected final void setExclusiveOwnerThread(Thread thread) {
  exclusiveOwnerThread = thread;
 }

 protected final Thread getExclusiveOwnerThread() {
 return exclusiveOwnerThread;
 }

}

上面這個exclusiveOwnerThread不就是獨占的owner線程的意思么?原來在這里。這就是3個小組件中的最后一個組件-owner****線程。

到這里你就可以得到如下所示的源碼層面的組件圖:

file

從JDK源碼層面理解AQS的線程第一次加鎖

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">從JDK源碼層面理解AQS的線程第一次加鎖</span></h3></div>

當你有了ReentrantLock加鎖過程的這個概念后,來分析下源碼就很簡單多了。

lock方法源碼如下:

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

直接調(diào)用了ReentrantLock的內(nèi)部類,Sync組件的lock方法,而Sync組件lock方法是抽象的。如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
   abstract void lock();
}

sync這變量,在之前的構(gòu)造函數(shù)中,實際創(chuàng)建的是AbstractQueuedSynchronizer(AQS)的子類:NonfairSync。所以找到對應的lock方法代碼如下:

  static final class NonfairSync extends Sync {
   final void lock() {
     if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
     else
       acquire(1);
   }

   protected final boolean tryAcquire(int acquires) {
     return nonfairTryAcquire(acquires);
   }
  }

首先進行的操作就是一個CAS,更新了volatile變量state,由0變?yōu)?。底層使用的是Unsafe類操作的。這個和Aotmic類的底層CAS沒什么區(qū)別,是類似的。

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

之后如果沒有別的線程并發(fā)進行CAS操作的話,這個修改state的CAS操作會成功,并且返回true。接著就會執(zhí)行setExclusiveOwnerThread方法了。這個方法代碼如下:

  protected final void setExclusiveOwnerThread(Thread thread) {
   exclusiveOwnerThread = thread;
  }

實際就是設置了當前加鎖的線程owner。接著整個lock方法就結(jié)束了。

final void lock() {
    if (compareAndSetState(0, 1))
      setExclusiveOwnerThread(Thread.currentThread());
    else
      acquire(1);
  }

最終你會得到如下所示的流程圖:

file

小結(jié)&思考

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">小結(jié)&思考</span></h3></div>

其實通過這節(jié),你可以發(fā)現(xiàn),其實ReentrantLock就是基于3個屬性來實現(xiàn)的,只不過是通過抽象類封裝了公共的屬性和操作,而這個抽象類常被我們成為AQS。

第一次加鎖其實主要就是

1、CAS操作一個volatile的int state從0->1

2、是指一個onwerThread為加鎖線程

3、如果沒有競爭的情況,和Queue隊列沒關系的。

大家學完一個技術(shù)后,一定要一會思考和提煉關鍵點、抽象思想,這個是非常重要的!

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容