成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入 ReentrantLock 內(nèi)部:公平鎖與非公平鎖之奧秘

開(kāi)發(fā) 前端
公平鎖的優(yōu)點(diǎn)是等待鎖的線程不會(huì)餓死。缺點(diǎn)是整體吞吐效率?相對(duì)非公平鎖要低,等待隊(duì)列中除第一個(gè)線程以外的所有線程都會(huì)阻塞,CPU喚醒阻塞線程的開(kāi)銷(xiāo)比非公平鎖大。

1 前言

在Java的JUC包中,提供了一個(gè)強(qiáng)大的鎖工具ReentrantLock,在多線程編程時(shí),我們會(huì)時(shí)常用到。而其中的公平鎖與非公平鎖更是有著獨(dú)特的魅力和重要的作用。理解公平鎖與非公平鎖的區(qū)別,對(duì)于優(yōu)化程序性能、確保資源的合理分配至關(guān)重要。

下面,我們將深入探討ReentrantLock的公平鎖與非公平鎖,帶你揭開(kāi)它們的神秘面紗,掌握多線程編程的關(guān)鍵技巧。那么接下來(lái),讓我們一起開(kāi)啟這場(chǎng)探索之旅吧!

2 公平 VS 非公平鎖

首先我們先來(lái)了解下什么是公平鎖和非公平鎖。

公平鎖:指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖。在公平鎖機(jī)制下,線程獲取鎖的順序是遵循先來(lái)后到的原則,就像在排隊(duì)一樣。

非公平鎖:指多個(gè)線程獲取鎖的順序是不確定的。當(dāng)一個(gè)線程釋放鎖后,新請(qǐng)求鎖的線程有可能立即獲取到鎖,而不管在它之前是否還有其他等待的線程。

3 ReentrantLock公平鎖和非公平鎖

3.1 繼承關(guān)系圖譜

圖片圖片

通過(guò)繼承關(guān)系圖譜,我們可以看到ReentrantLock類(lèi)實(shí)現(xiàn)了Serializable接口和Lock接口,另外其內(nèi)部定義了3個(gè)內(nèi)部類(lèi)Sync、NonfairSync、FairSync。Sycn是一個(gè)抽象類(lèi)實(shí)現(xiàn)了AbstractQueuedSynchronizer(下文簡(jiǎn)稱(chēng)AQS),NonfairSync、FairSync為Sync的實(shí)現(xiàn)子類(lèi),通過(guò)類(lèi)的命名其實(shí)我們就可以知道NonfairSync為非公平鎖的實(shí)現(xiàn)類(lèi),F(xiàn)airSync為公平鎖的實(shí)現(xiàn)類(lèi),而Sycn為抽象出來(lái)的公共抽象類(lèi)。

3.2 創(chuàng)建公平鎖與非公平鎖

ReentrantLock中提供了兩個(gè)構(gòu)造函數(shù),一個(gè)是默認(rèn)的構(gòu)造函數(shù),另一個(gè)是有參構(gòu)造函數(shù),通過(guò)布爾值參數(shù)控制創(chuàng)建鎖對(duì)象的類(lèi)型??梢钥吹绞褂媚J(rèn)構(gòu)造函數(shù)默認(rèn)創(chuàng)建的是非公平鎖,使用有參構(gòu)造函數(shù)參數(shù)為true時(shí),創(chuàng)建的為公平鎖,參數(shù)為false時(shí),創(chuàng)建的為非公平鎖。

/**
    * 無(wú)參構(gòu)造器
    * 說(shuō)明:從構(gòu)造器內(nèi)部實(shí)現(xiàn)可知默認(rèn)構(gòu)造的鎖為非公平鎖
    */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
    * 有參構(gòu)造器
    * 說(shuō)明:fair參數(shù)設(shè)定構(gòu)造的對(duì)象是公平鎖還是非公平鎖
    *      true:公平鎖
    *      false:非公平鎖
    */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

3.3 使用示例

3.3.1 非公平鎖

@Test
public void testUnfairLock() throws InterruptedException {
    // 無(wú)參構(gòu)造函數(shù),默認(rèn)創(chuàng)建非公平鎖模式
    ReentrantLock lock = new ReentrantLock();

    for (int i = 0; i < 6; i++) {
        final int threadNum = i;
        new Thread(() -> {
            //獲取鎖
            lock.lock();
            try {
                System.out.println("線程" + threadNum + "獲取鎖");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // finally中解鎖
                lock.unlock();
                System.out.println("線程" + threadNum +"釋放鎖");
            }
        }).start();
        Thread.sleep(999);
    }

    Thread.sleep(100000);
}

運(yùn)行結(jié)果:

線程0獲取鎖
線程0釋放鎖
線程1獲取鎖
線程1釋放鎖
線程3獲取鎖
線程3釋放鎖
線程2獲取鎖
線程2釋放鎖
線程5獲取鎖
線程5釋放鎖
線程4獲取鎖
線程4釋放鎖

3.3.2 公平鎖

@Test
public void testfairLock() throws InterruptedException {
    // 有參構(gòu)造函數(shù),true表示公平鎖,false表示非公平鎖
    ReentrantLock lock = new ReentrantLock(true);

    for (int i = 0; i < 6; i++) {
        final int threadNum = i;
        new Thread(() -> {
            //獲取鎖
            lock.lock();
            try {
                System.out.println("線程" + threadNum + "獲取鎖");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // finally中解鎖
                lock.unlock();
                System.out.println("線程" + threadNum +"釋放鎖");
            }
        }).start();
        Thread.sleep(10);
    }

    Thread.sleep(100000);
}

運(yùn)行結(jié)果:

線程0獲取鎖
線程0釋放鎖
線程1獲取鎖
線程1釋放鎖
線程2獲取鎖
線程2釋放鎖
線程3獲取鎖
線程3釋放鎖
線程4獲取鎖
線程4釋放鎖
線程5獲取鎖
線程5釋放鎖

3.4 實(shí)現(xiàn)原理分析

接下來(lái),我們從ReentrantLock提供的兩個(gè)核心API加鎖方法lock()和解鎖方法unlock()為入口,繼續(xù)深入探索其內(nèi)部公平鎖和非公平鎖的實(shí)現(xiàn)原理。

3.4.1 加鎖流程剖析

1)ReentrantLock.lock()方法為ReentrantLock提供的加鎖方法。公平鎖和非公平鎖都可以通過(guò)該方法來(lái)獲取鎖,區(qū)別在于其內(nèi)部的sync引用的實(shí)例對(duì)象不同,公平鎖時(shí),sync引用的為FairSync對(duì)象,非公平鎖時(shí),sync引用的為NonfairSync對(duì)象。

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

2)那FairSync和NonfairSync中l(wèi)ock()方法的具體實(shí)現(xiàn)有哪些不同呢?

通過(guò)下面的代碼對(duì)比我們可以看到FairSync.lock()方法實(shí)現(xiàn)是直接調(diào)用了AQS提供的acquire()方法。而NonfairSync.lock()方法實(shí)現(xiàn)是先通過(guò)CAS的方式先嘗試獲取了一次鎖,如果嘗試成功則直接將當(dāng)前線程設(shè)置為占用鎖線程,而獲取失敗時(shí)同樣調(diào)用了AQS提供的acquire()方法。從這里可以看到非公平鎖獲取鎖時(shí),如果當(dāng)前鎖未被其他任何線程占用時(shí),當(dāng)前線程是有一次機(jī)會(huì)直接獲取到鎖的,而從公平鎖的方法實(shí)現(xiàn)中我們還無(wú)法看到公平鎖是如何實(shí)現(xiàn),那我們繼續(xù)深入看下AQS提供的acquire()方法的實(shí)現(xiàn)。

/**
*  FairSync.lock()方法實(shí)現(xiàn)
**/
final void lock() {
   //調(diào)用的AQS中提供的的實(shí)現(xiàn)獨(dú)占鎖方法
   acquire(1);
}
/**
*  NonfairSync.lock()方法實(shí)現(xiàn)
**/
final void lock() {
   //通過(guò)CAS的方式嘗試獲取鎖
   if (compareAndSetState(0, 1))
      //獲取鎖成功則將當(dāng)前線程設(shè)置為占用鎖線程
      setExclusiveOwnerThread(Thread.currentThread());
   else
      //未成功獲取到鎖,調(diào)用AQS中的acquire()方法,再次嘗試獲取鎖
      acquire(1);
}

3)AbstractQueuedSynchronizer.acquire()方法,該方法是AQS實(shí)現(xiàn)獨(dú)占鎖的核心方法,主要的邏輯都在if判斷條件中,這里面有3個(gè)重要的方法tryAcquire(),addWaiter()和acquireQueued()。這三個(gè)方法中分別封裝了加鎖流程中的主要處理邏輯。

方法中首先調(diào)用了tryAcquire()方法進(jìn)行嘗試獲取鎖。如果嘗試獲取失敗則會(huì)調(diào)用addWaiter()方法將獲取鎖失敗的線程加入到等待隊(duì)列中,然后將addWaiter()方法返回的結(jié)果作為參數(shù),繼續(xù)調(diào)用acquireQueued()方法,此時(shí)當(dāng)前線程會(huì)不斷嘗試獲取鎖,當(dāng)獲取鎖成功后則會(huì)調(diào)用selfInterrupt()方法喚醒線程繼續(xù)執(zhí)行。

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

我們繼續(xù)層層剖析!分別看下tryAcquire(),addWaiter()和acquireQueued()的源碼實(shí)現(xiàn)。

4)AbstractQueuedSynchronizer.tryAcquire()方法,該方法默認(rèn)拋出了UnsupportedOperationException異常,自身未提供具體實(shí)現(xiàn),此方法為AQS提供的鉤子模版方法,由子類(lèi)同步組件通過(guò)擴(kuò)展該方法實(shí)現(xiàn)嘗試獲取鎖邏輯。FairSync和NonfairSync分別重寫(xiě)了該方法并提供了不同的實(shí)現(xiàn)。

protected boolean tryAcquire(int arg) {
  throw new UnsupportedOperationException();
}

5)FairSync和NonfairSync中tryAcquire()方法重寫(xiě)實(shí)現(xiàn)。

通過(guò)下圖中的源碼對(duì)比,我們可以明顯的看出公平鎖與非公平鎖主要區(qū)別就在于公平鎖在獲取同步狀態(tài)時(shí)多了一個(gè)限制條件:hasQueuedPredecessors(),而其他的代碼流程是基本一致的,那我們?cè)龠M(jìn)入hasQueuedPredecessors()方法看一下。

/**
*  FairSync.lock()方法實(shí)現(xiàn)
**/
protected final boolean tryAcquire(int acquires) {

   //獲取當(dāng)前線程對(duì)象
   final Thread current = Thread.currentThread();
   
   //獲取當(dāng)前鎖的狀態(tài)
   int c = getState();
   
   //狀態(tài)為0時(shí)表示鎖未被占用
   if (c == 0) { 
   
   //首先調(diào)用hasQueuedPredecessors()方法,檢查隊(duì)列中是否存在等待執(zhí)行的線程,如果隊(duì)列中有待執(zhí)行的線程,會(huì)優(yōu)先讓隊(duì)列中的線程執(zhí)行,這是公平鎖實(shí)現(xiàn)的核心
   if (!hasQueuedPredecessors() &&
          //如果hasQueuedPredecessors()這個(gè)方法返回false,則表示隊(duì)列中沒(méi)有等待執(zhí)行的線程,那么會(huì)繼續(xù)調(diào)用compareAndSetState(0, acquires)方法,通過(guò)cas嘗試獲取鎖
          compareAndSetState(0, acquires)) {
          
       //如果獲取鎖成功,設(shè)置當(dāng)前線程對(duì)象為占用鎖的線程對(duì)象
       setExclusiveOwnerThread(current);
       
       //返回獲取鎖成功
       return true;
   }
   
   //如果 current == getExclusiveOwnerThread() 相等,說(shuō)明當(dāng)前線程與占用鎖的線程是是同一個(gè)線程,則也會(huì)被認(rèn)為獲取鎖成功,即:重入鎖
   } else if (current == getExclusiveOwnerThread()) {
       //疊加重入次數(shù)
       int nextc = c + acquires;
       if (nextc < 0)
          throw new Error("Maximum lock count exceeded");
       //更新鎖重入次數(shù)
       setState(nextc);
       //返回獲取鎖成功
       return true;
    }
    //返回獲取鎖失敗
    return false;
}
/**
*  NonfairSync.lock()方法
**/
protected final boolean tryAcquire(int acquies) {
  //繼續(xù)調(diào)用父抽象類(lèi)Sync類(lèi)中的nonfairTryAcquire方法
  return nonfairTryAcquire(acquires);
}


/**
*  Sync.nonfairTryAcquire()方法
**/
final boolean nonfairTryAcquire(int acquires) {
    //獲取當(dāng)前線程對(duì)象實(shí)例
    final Thread current = Thread.currentThread();
    //獲取state變量的值,即當(dāng)前鎖被重入的次數(shù)
    int c = getState();
    //state值為0,說(shuō)明當(dāng)前鎖未被任何線程持有
    if (c == 0) {
        //以cas方式獲取鎖
        if (compareAndSetState(0, acquires)) {
            //獲取鎖成功,將當(dāng)前線程標(biāo)記為持有鎖的線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果當(dāng)前線程就是持有鎖的線程,說(shuō)明該鎖被重入了
    else if (current == getExclusiveOwnerThread()) {
        //疊加重入次數(shù)
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //更新重入次數(shù)
        setState(nextc);
        return true;
    }
    //走到這里說(shuō)明嘗試獲取鎖失敗
    return false;
}

6)FairSync.hasQueuedPredecessors()方法,可以看到該方法主要做一件事情:主要是判斷檢查隊(duì)列是否存在等待執(zhí)行的線程,并且頭部等待線程非當(dāng)前線程。如果是則返回true,否則返回false。該方法也是公平鎖實(shí)現(xiàn)的核心。當(dāng)隊(duì)列中已存在其他等待中的線程時(shí),則會(huì)獲取鎖失敗,會(huì)調(diào)用AbstractQueuedSynchronizer.addWaiter()方法將當(dāng)前線程放入等待隊(duì)列的尾部來(lái)排隊(duì)獲取鎖。

/**
* 判斷檢查隊(duì)列頭部是否存在等待執(zhí)行的線程,并且等待線程非當(dāng)前線程
*
* @return 
*/
public final boolean hasQueuedPredecessors() {
   Node t = tail;
   Node h = head;
   Node s;
   
   /**
   * h != t,如果頭結(jié)點(diǎn)等于尾節(jié)點(diǎn),說(shuō)明隊(duì)列中無(wú)數(shù)據(jù),則說(shuō)明隊(duì)列中沒(méi)有等待處理的節(jié)點(diǎn)
   * (s = h.next) == null,頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為空 返回true
   * s.thread != Thread.currentThread() 頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)(即將執(zhí)行的節(jié)點(diǎn))所擁有的線程不是當(dāng)前線程,返回true,說(shuō)明隊(duì)列中有即將執(zhí)行的節(jié)點(diǎn)。
   */
   return h != t &&
          ((s = h.next) == null || s.thread != Thread.currentThread());
}

為了方便大家理解,下面羅列了此方法返回true和返回false的場(chǎng)景圖解:

圖片圖片

7)AbstractQueuedSynchronizer.addWaiter()方法,該方法主要是將獲取鎖失敗的線程加入到等待隊(duì)列的尾部,也就是進(jìn)行排隊(duì),如果隊(duì)列已初始化完成則直接將線程加入到隊(duì)列尾部,如果隊(duì)列尚未初始化,則會(huì)調(diào)用AbstractQueuedSynchronizer.enq()方法來(lái)完成隊(duì)列的初始化再將當(dāng)前線程加入到隊(duì)列尾部。

/**
* 將獲取鎖失敗的線程加入到等待隊(duì)列中
*
* return 返回新加入的節(jié)點(diǎn)對(duì)象
*/
private Node addWaiter(Node mode) {
  
  //創(chuàng)建新的節(jié)點(diǎn),設(shè)置節(jié)點(diǎn)線程為當(dāng)前線程,模式為獨(dú)占模式
  Node node = new Node(Thread.currentThread(), mode);
  
  //pred引用尾節(jié)點(diǎn)
  Node pred = tail;
  
  //判定是否有尾節(jié)點(diǎn)
  if (pred != null) {
     //存在尾節(jié)點(diǎn)將當(dāng)前節(jié)點(diǎn)的前驅(qū)指針指向尾節(jié)點(diǎn)
     node.prev = pred;
     //通過(guò)cas將當(dāng)前節(jié)點(diǎn)設(shè)置為尾幾點(diǎn),當(dāng)期望尾節(jié)點(diǎn)為pred時(shí),則將當(dāng)前node節(jié)點(diǎn)更新為尾節(jié)點(diǎn)
     if (compareAndSetTail(pred, node)) {
        //將原尾節(jié)點(diǎn)的后繼指針指向當(dāng)前節(jié)點(diǎn),這里是雙向鏈表 node.prev = pred; pred.next = node; 構(gòu)成雙向鏈表
        pred.next = node;
        //設(shè)置成功返回當(dāng)前節(jié)點(diǎn)
        return node;
     }
  }
  //如果沒(méi)有尾節(jié)點(diǎn)說(shuō)明隊(duì)列還未初始化,那么將進(jìn)行初始化,并將當(dāng)前節(jié)點(diǎn)添加值隊(duì)列尾部
  enq(node);
  return node;
}

流程圖解:

圖片圖片

8)AbstractQueuedSynchronizer.enq()方法,初始化隊(duì)列,并將當(dāng)前節(jié)點(diǎn)追加到隊(duì)列尾部,如果已經(jīng)初始化完成則直接追加。

/**
* 初始化隊(duì)列,并將當(dāng)前節(jié)點(diǎn)追加到隊(duì)列尾部,如果已經(jīng)初始化完成則直接追加
* 
* return 節(jié)點(diǎn)對(duì)象
*/
private Node enq(final Node node) {

   //死循環(huán),直到插入隊(duì)列成功跳出
   for (;;) {
      Node t = tail;
      //判斷尾節(jié)點(diǎn)是否為空,如果為空則說(shuō)明當(dāng)前隊(duì)列未進(jìn)行初始化,則需進(jìn)行初始化操作
      if (t == null) {
          //新建一個(gè)空節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)
          if (compareAndSetHead(new Node()))
            //尾節(jié)點(diǎn)指向頭節(jié)點(diǎn),此時(shí)尾節(jié)點(diǎn)與頭結(jié)點(diǎn)為同一個(gè)節(jié)點(diǎn)
            tail = head;
          } else {
            //如果不為空則說(shuō)明已經(jīng)初始化完成,直接將當(dāng)前節(jié)點(diǎn)插入尾部,構(gòu)成雙向鏈表  node.prev = t;t.next = node;
            node.prev = t;
            //設(shè)置當(dāng)前節(jié)點(diǎn)為尾節(jié)點(diǎn)
            if (compareAndSetTail(t, node)) {
               //設(shè)置原尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn)
               t.next = node;
               return t;
            }
          }
        }
    }

流程圖解:

9)AbstractQueuedSynchronizer.acquireQueued()方法,將線程加入到隊(duì)列尾部后,加入線程會(huì)不斷嘗試獲取鎖,直到獲取成功或者不再需要獲取(中斷)。

該方法的實(shí)現(xiàn)分成兩部分:

9.1)如果當(dāng)前節(jié)點(diǎn)已經(jīng)成為頭結(jié)點(diǎn),嘗試獲取鎖(tryAcquire)成功,然后返回。

9.2)否則檢查當(dāng)前節(jié)點(diǎn)是否應(yīng)該被park(等待),將該線程park并且檢查當(dāng)前線程是否被可以被中斷。

/**
* 不斷嘗試(自旋)進(jìn)行獲取鎖,直到獲取成功或者不再需要獲取(中斷)
*
* return
*/
final boolean acquireQueued(final Node node, int arg) {

      //標(biāo)記是否成功獲取到鎖,默認(rèn)為未獲取到
      boolean failed = true;
      try {
          //標(biāo)記是否需要喚醒中斷線程,線程是否處于中斷狀態(tài)
          boolean interrupted = false;
          //開(kāi)始自旋,要么獲取到鎖,要么線程被中斷掛起
          for (; ; ) {
          
             //獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
             final Node p = node.predecessor();
             
             //判斷前驅(qū)節(jié)點(diǎn)是否為頭節(jié)點(diǎn),如果為頭節(jié)點(diǎn),則說(shuō)明當(dāng)前線程為排隊(duì)第一的待執(zhí)行節(jié)點(diǎn),可以嘗試獲取鎖
             if (p == head && tryAcquire(arg)) {
                //如果獲取鎖成功將當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn)
                setHead(node);
                //將原頭節(jié)點(diǎn)的后繼指針設(shè)為null,去除強(qiáng)引用關(guān)系,幫助GC回收
                p.next = null;
                //標(biāo)記獲取鎖成功 failed = false
                failed = false;
                
                //返回當(dāng)前線程的中斷標(biāo)記,是否需要喚醒當(dāng)前線程
                return interrupted;
             }
             //檢查當(dāng)前節(jié)點(diǎn)是否應(yīng)該被阻塞等待park
             if (shouldParkAfterFailedAcquire(p, node) &&
                        //設(shè)置當(dāng)前線程進(jìn)入阻塞狀態(tài)
                        parkAndCheckInterrupt())
                //標(biāo)記當(dāng)前線程的中斷狀態(tài)為中斷掛起狀態(tài),線程再次執(zhí)行需要被喚醒。
                interrupted = true;
            }
      } finally {
          if (failed)
            //只有在出異常的情況下才會(huì)執(zhí)行到這里,需要將當(dāng)前節(jié)點(diǎn)取消掉
            cancelAcquire(node);
      }
}

3.4.2 解鎖流程剖析

1)ReentrantLock.unlock()方法為ReentrantLock提供的解鎖方法。從實(shí)現(xiàn)可以看到該方法繼續(xù)調(diào)用了release()方法,而NonFairLock、FairLock和Sync類(lèi)中均未重寫(xiě)release()方法,所以此處是直接調(diào)用了AQS提供的release()方法來(lái)進(jìn)行的解鎖操作。

public void unlock() {
   sync.release(1);
}

2)AbstractQueuedSynchronizer.release()方法,此方法主要做了兩個(gè)事情,首先是通過(guò)調(diào)用tryRelease()方法嘗試釋放鎖,如果釋放失敗直接返回失敗,如果鎖釋放成功則會(huì)去喚醒下個(gè)節(jié)點(diǎn)線程的執(zhí)行。下面,我們繼續(xù)先看下tryRelease()方法的實(shí)現(xiàn)。

/**
 * 鎖釋放 并喚醒下一個(gè)節(jié)點(diǎn)
 *
 * @param arg
 * @return
 */
public final boolean release(int arg) {
    // 1.嘗試釋放鎖
    if (tryRelease(arg)) {
        Node h = head;
        //2.頭結(jié)點(diǎn)不為null并且等待狀態(tài)不是初始化狀態(tài),因?yàn)樘幱诔跏蓟癄顟B(tài)的節(jié)點(diǎn)可能仍未初始化完成
        if (h != null && h.waitStatus != 0)
            //3.喚醒頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    //嘗試獲取鎖失敗
    return false;
}

3)AbstractQueuedSynchronizer.tryRelease()方法,可以看到此方法同AbstractQueuedSynchronizer.tryAcquiry()方法一樣,是由子類(lèi)決定具體實(shí)現(xiàn)的,我們回看下ReentrantLock中定義的內(nèi)部類(lèi),可以看到Sync類(lèi)中重寫(xiě)了該方法,而NonFairLock和FairLock方法中并未再次重寫(xiě)該方法。所以在調(diào)用AQS中的tryRelease()方法時(shí)其實(shí)是調(diào)用的Sync類(lèi)中的tryRelease()方法。

protected boolean tryRelease(int arg) {
   throw new UnsupportedOperationException();
}

4)Sync.tryRelease()方法,該方法做的事其實(shí)跟簡(jiǎn)單主要是將已執(zhí)行完成的線程持有的鎖資源釋放掉。

/**
 * 已執(zhí)行完成的線程,釋放資源
 *
 * @param releases
 * @return 返回釋放鎖后的已重入次數(shù)
 */
protected final boolean tryRelease(int releases) {
    //獲取當(dāng)前資源狀態(tài)值,并重新計(jì)算已重入次數(shù)
    int c = getState() - releases;
    //如果當(dāng)前線程不是獲得資源的線程,將拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    //資源是否完全釋放,因?yàn)樯婕暗娇芍厝腈i
    boolean free = false;
    if (c == 0) {
        //等于0的情況下表示資源完全釋放
        free = true; 
        //清除鎖的持有線程標(biāo)記
        setExclusiveOwnerThread(null);
    }
    //重新設(shè)置已重入次數(shù)
    setState(c);
    return free;
}

5)Sync.unparkSuccessor()方法,鎖釋放成功后會(huì)調(diào)用該方法,來(lái)喚醒當(dāng)前節(jié)點(diǎn)的后續(xù)節(jié)點(diǎn)線程的執(zhí)行。

/**
 * 喚醒當(dāng)前節(jié)點(diǎn)的后續(xù)節(jié)點(diǎn)
 *
 * @param node the node
 */
private void unparkSuccessor(Node node) {

    //獲取頭結(jié)點(diǎn)的等待狀態(tài)
    int ws = node.waitStatus;
    
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //獲取當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
    Node s = node.next;
    
    //如果當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是null或者狀態(tài)大于0,說(shuō)明當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不是有效節(jié)點(diǎn),那么則需要找到下一個(gè)有效的等待節(jié)點(diǎn)
    if (s == null || s.waitStatus > 0) {
        s = null;
        //從尾節(jié)點(diǎn)開(kāi)始向前找,找到最前面的狀態(tài)小于0的節(jié)點(diǎn)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //喚醒讓當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)線程,繼續(xù)執(zhí)行
        LockSupport.unpark(s.thread);
}

4 總結(jié)

從實(shí)現(xiàn)來(lái)看,公平鎖的實(shí)現(xiàn)利用了FIFO隊(duì)列的特性,先加入同步隊(duì)列等待的線程會(huì)比后加入的線程更靠近隊(duì)列的頭部,那么它將比后者更早的被喚醒,它也就能更早的得到鎖。從這個(gè)意義上,對(duì)于在同步隊(duì)列中等待的線程而言,它們獲得鎖的順序和加入同步隊(duì)列的順序一致,這顯然是一種公平模式。然而,線程并非只有在加入隊(duì)列后才有機(jī)會(huì)獲得鎖,哪怕同步隊(duì)列中已有線程在等待,非公平鎖的不公平之處就在于此?;乜聪路枪芥i的加鎖流程,線程在進(jìn)入同步隊(duì)列等待之前有兩次搶占鎖的機(jī)會(huì)。

  • 第一次是非重入式的獲取鎖,只有在當(dāng)前鎖未被任何線程占有(包括自身)時(shí)才能成功。

圖片圖片

  • 第二次是在進(jìn)入同步隊(duì)列前,包含所有情況的獲取鎖的方式。

圖片圖片

只有這兩次獲取鎖都失敗后,線程才會(huì)構(gòu)造結(jié)點(diǎn)并加入到同步隊(duì)列等待,而線程釋放鎖時(shí)是先釋放鎖(修改state值),然后才喚醒后繼結(jié)點(diǎn)的線程的。試想下這種情況,線程A已經(jīng)釋放鎖,但還沒(méi)來(lái)得及喚醒后繼線程C,而這時(shí)另一個(gè)線程B剛好嘗試獲取鎖,此時(shí)鎖恰好不被任何線程持有,它將成功獲取鎖而不用加入隊(duì)列等待。線程C被喚醒嘗試獲取鎖,而此時(shí)鎖已經(jīng)被線程B搶占,故而其獲取失敗并繼續(xù)在隊(duì)列中等待。如果以線程第一次嘗試獲取鎖到最后成功獲取鎖的次序來(lái)看,非公平鎖確實(shí)很不公平。因?yàn)樵陉?duì)列中等待很久的線程相比于還未進(jìn)入隊(duì)列等待的線程并沒(méi)有優(yōu)先權(quán),甚至競(jìng)爭(zhēng)也處于劣勢(shì),在隊(duì)列中的線程要等待其他線程喚醒,在獲取鎖之前還要檢查前驅(qū)結(jié)點(diǎn)是否為頭結(jié)點(diǎn)。在鎖競(jìng)爭(zhēng)激烈的情況下,在隊(duì)列中等待的線程可能遲遲競(jìng)爭(zhēng)不到鎖。這也就非公平在高并發(fā)情況下會(huì)出現(xiàn)的饑餓問(wèn)題。

5 思考

5.1 為什么非公平鎖性能好

非公平鎖對(duì)鎖的競(jìng)爭(zhēng)是搶占式的(隊(duì)列中線程除外),線程在進(jìn)入等待隊(duì)列前可以進(jìn)行兩次嘗試,這大大增加了獲取鎖的機(jī)會(huì)。這種好處體現(xiàn)在兩個(gè)方面:

1)線程不必加入等待隊(duì)列就可以獲得鎖,不僅免去了構(gòu)造結(jié)點(diǎn)并加入隊(duì)列的繁瑣操作,同時(shí)也節(jié)省了線程阻塞喚醒的開(kāi)銷(xiāo),線程阻塞和喚醒涉及到線程上下文的切換和操作系統(tǒng)的系統(tǒng)調(diào)用,是非常耗時(shí)的。在高并發(fā)情況下,如果線程持有鎖的時(shí)間非常短,短到線程入隊(duì)阻塞的過(guò)程超過(guò)線程持有并釋放鎖的時(shí)間開(kāi)銷(xiāo),那么這種搶占式特性對(duì)并發(fā)性能的提升會(huì)更加明顯。

2)減少CAS競(jìng)爭(zhēng),如果線程必須要加入阻塞隊(duì)列才能獲取鎖,那入隊(duì)時(shí)CAS競(jìng)爭(zhēng)將變得異常激烈,CAS操作雖然不會(huì)導(dǎo)致失敗線程掛起,但不斷失敗重試導(dǎo)致的對(duì)CPU的浪費(fèi)也不能忽視。

5.2 公平鎖與非公平鎖的選擇

公平鎖的優(yōu)點(diǎn)是等待鎖的線程不會(huì)餓死。缺點(diǎn)是整體吞吐效率相對(duì)非公平鎖要低,等待隊(duì)列中除第一個(gè)線程以外的所有線程都會(huì)阻塞,CPU喚醒阻塞線程的開(kāi)銷(xiāo)比非公平鎖大。所以適用場(chǎng)景適用于對(duì)資源訪問(wèn)順序有嚴(yán)格要求的場(chǎng)景。例如,在一些資源分配系統(tǒng)中,要求按照請(qǐng)求的先后順序來(lái)分配資源,以避免饑餓現(xiàn)象(某個(gè)線程一直無(wú)法獲取鎖)的發(fā)生。

非公平鎖的優(yōu)點(diǎn)是可以減少喚起線程的開(kāi)銷(xiāo),整體的吞吐效率高,因?yàn)榫€程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線程。缺點(diǎn)是處于等待隊(duì)列中的線程可能會(huì)餓死,或者等很久才會(huì)獲得鎖。所以非公平鎖適用于如果對(duì)線程獲取鎖的順序沒(méi)有嚴(yán)格要求的場(chǎng)景,例如在一些高并發(fā)的緩存系統(tǒng)或者日志系統(tǒng)中,可以使用非公平鎖來(lái)提高系統(tǒng)的整體性能。


關(guān)于作者孔德志  采貨俠Java開(kāi)發(fā)工程師

責(zé)任編輯:武曉燕 來(lái)源: 轉(zhuǎn)轉(zhuǎn)技術(shù)
相關(guān)推薦

2022-12-26 00:00:04

公平鎖非公平鎖

2020-08-24 08:13:25

非公平鎖源碼

2022-07-12 08:56:18

公平鎖非公平鎖Java

2019-01-04 11:18:35

獨(dú)享鎖共享鎖非公平鎖

2018-07-31 15:05:51

Java公平鎖線程

2022-05-09 07:37:04

Java非公平鎖公平鎖

2023-10-07 08:17:40

公平鎖非公平鎖

2021-08-20 07:54:20

非公平鎖 Java多線編程

2021-07-02 08:51:09

Redisson分布式鎖公平鎖

2021-06-30 14:56:12

Redisson分布式公平鎖

2021-07-01 09:42:08

Redisson分布式

2022-06-15 15:14:17

Java公平鎖非公平鎖

2022-12-08 17:15:54

Java并發(fā)包

2021-06-02 21:31:39

Synchronous非公平模式

2024-01-29 15:54:41

Java線程池公平鎖

2021-05-11 14:50:21

ReentrantLo可重入鎖Java

2022-04-14 07:56:30

公平鎖Java線程

2023-07-06 08:06:47

LockCondition公平鎖

2021-06-30 11:33:02

智慧城市物聯(lián)網(wǎng)

2020-09-16 10:59:44

AI人工智能AI系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产在线精品一区二区 | 日韩成人高清 | 一区二区视频在线观看 | 久久精品青青大伊人av | 欧美精品1区 | 最新av中文字幕 | 欧美a区 | 国产在线一区二区三区 | 亚洲成人免费电影 | 91玖玖| 亚洲精品久久久9婷婷中文字幕 | 一本岛道一二三不卡区 | 综合九九 | 亚洲自拍一区在线观看 | 亚洲欧美综合网 | 日韩成人高清在线 | 日韩午夜在线观看 | 97精品国产97久久久久久免费 | 一区二区影视 | 99re视频在线 | 欧美精品在欧美一区二区 | 国产精品久久久一区二区三区 | 国产精品美女久久久久久免费 | 中国一级特黄真人毛片免费观看 | 精品一区二区在线视频 | 一片毛片 | 欧美日韩中文字幕 | 国产一区二区三区四区五区加勒比 | 国产色在线 | 日本久久久一区二区三区 | .国产精品成人自产拍在线观看6 | 一本一道久久a久久精品综合蜜臀 | 噜久寡妇噜噜久久寡妇 | 国产片一区二区三区 | 亚洲精品在线播放 | xxxxx免费视频 | 国产日韩欧美在线观看 | 久久久视 | 亚洲精品大片 | 亚洲人精品午夜 | 精品美女视频在线观看免费软件 |