常用併發工具原理分析

常用併發工具原理分析

一、包含的知識點

  • Condition原理
  • CountDownLatch原理
  • CyclicBarrier原理
  • Semaphore原理

二、Condition原理

2.1 Condition簡單使用

​ 沒有Lock之前, 線程間通信可以通過wait()/notify()、notifyAll()來進行通信, 在Doug Lea提供了Lock之後, 有沒有新的方式進行線程間通信了呢 ? 答案肯定是有的。 它的實現方式可以參考下面的代碼

//await實現
public class ConditionWait implements Runnable{
    private Lock lock ;
    private Condition condition ;

    public ConditionWait(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        System.out.println("ConditionWait start ...");

        try {
            lock.lock();
            condition.await();
            System.out.println("ConditionWait await ...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

        System.out.println("ConditionWait end ...");
    }
}

//signal實現
public class ConditionSignal implements Runnable {
    private Lock lock ;
    private Condition condition ;

    public ConditionSignal(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        System.out.println("ConditionSignal start ...");

        try{
            lock.lock();
            condition.signal();
            System.out.println("ConditionSignal signal ...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

        System.out.println("ConditionSignal end ...");
    }
}

//main
public class ConditionMain {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition() ;

        ConditionWaitDemo waitDemo = new ConditionWaitDemo(lock, condition) ;
        ConditionSignalDemo signalDemo = new ConditionSignalDemo(lock, condition) ;

        new Thread(waitDemo).start();
        new Thread(signalDemo).start();

        Thread.sleep(3000);
    }
}

​ Condition調用await方法後, 當前線程會釋放鎖並等待, 當其它線程調用Condition.signal/signalAll方法,會通知被阻塞的線程, 被喚醒的線程在獲得鎖之後會繼續執行。

​ 上面的代碼是通過Condition實現wait/notify功能, 除此之外,也可以通過Condition實現生產者(Producer)/消費者(Consumer)等其它功能, 具體實現邏輯大家可以Google查詢一下。

2.2 Condition await源碼分析

​ 從2.1節ConditionWait代碼可以看到, 線程執行await之前需要獲得鎖, 執行wait操作時, 再調用await方法

lock.lock();
condition.await();

condition.await()

​ 首先我們看下await()的實現邏輯

public final void await() throws InterruptedException {
  if (Thread.interrupted()) // 檢測當前線程是否被中斷了
    throw new InterruptedException();
  Node node = addConditionWaiter(); // 創建一個狀態爲CONDITION的節點
  int savedState = fullyRelease(node); //釋放當前線程持有的鎖, 因爲鎖具有重入性, 這裏釋放鎖時會將state清0, savedState保存重入次數記錄
  int interruptMode = 0;
  while (!isOnSyncQueue(node)) { // 如果當前線程沒有在同步隊列上, 就將當前線程掛起
    LockSupport.park(this); // 掛起當前線程
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
      break;
  }
  /** 
  	* 當線程被喚醒時, 會嘗試拿鎖
  	* 如果獲取鎖成功, 並且中斷模式 != THROW_IE(-1), 將中斷模式設置爲REINTERRUPT(1)
    */
  if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;
  // 如果當前節點存在後續節點, 會將後續節點中CANCELLED狀態的給清理掉
  if (node.nextWaiter != null) // clean up if cancelled
    unlinkCancelledWaiters();
  if (interruptMode != 0) // 如果線程被中斷, 需要拋出異常或重新中斷
    reportInterruptAfterWait(interruptMode);
}
  • 首先會檢測當前線程是否被中斷了, 如果中斷了會拋出異常
  • 創建一個狀態爲CONDITION的節點, 並釋放當前線程持有的鎖
  • 如果當前線程沒有在同步隊列上, 就將當前線程掛起
  • 如果喚醒的線程獲取鎖成功, 並且中斷模式 != THROW_IE(-1), 就將中斷模式設置爲REINTERRUPT(1)
  • 如果當前節點存在後續節點, 會將後續節點中CANCELLED狀態的給清理掉

addConditionWaiter()

​ 將當前線程創建爲節點狀態爲CONDITION的新Node, , 並添加到等待隊列中, 這個隊列是單向鏈表, firstWaiter指向隊列首節點, lastWaiter指向隊列的尾節點

/**
	* Adds a new waiter to wait queue.
  * @return its new wait node
  */
private Node addConditionWaiter() {
  Node t = lastWaiter; // 獲取鏈表中尾節點
  // If lastWaiter is cancelled, clean out.
  if (t != null && t.waitStatus != Node.CONDITION) { // 如果尾節點不是CONDITION類型, 清除CANCELLED狀態節點
    unlinkCancelledWaiters();
    t = lastWaiter;
  }
  Node node = new Node(Thread.currentThread(), Node.CONDITION); // 將當前線程創建爲狀態爲CONDITION的新節點
  if (t == null) // 鏈表是空鏈表
    firstWaiter = node;
  else // 鏈表非空, 原尾節點指向當前新節點
    t.nextWaiter = node;
  lastWaiter = node; // 將當前節點設置爲新的尾節點
  return node;
}

fullyRelease()

​ 釋放當前線程鎖, 並清除重入記錄state, 返回當前線程的重入記錄值savedState, 注意和tryRelease()方法的區別

/**
	* Invokes release with current state value; returns saved state.
  * Cancels node and throws exception on failure.
  * @param node the condition node for this wait
  * @return previous sync state
  */
final int fullyRelease(Node node) {
  boolean failed = true;
  try {
    int savedState = getState(); // 獲取重入次數
    if (release(savedState)) { //釋放鎖, 並喚醒同步隊列中下一個線程
      failed = false;
      return savedState;
    } else {
      throw new IllegalMonitorStateException();
    }
  } finally {
    if (failed) // 如果釋放鎖失敗, 將當前設置爲CANCELLED狀態
      node.waitStatus = Node.CANCELLED;
  }
}

//ReentrantLock原理分析.md 已經分析了這個方法
public final boolean release(int arg) {
  if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
      unparkSuccessor(h); // 釋放鎖成功, 同步隊列喚醒後續節點
    return true;
  }
  return false;
}

isOnSyncQueue()

final boolean isOnSyncQueue(Node node) {
  if (node.waitStatus == Node.CONDITION || node.prev == null)
    return false;
  if (node.next != null) // If has successor, it must be on queue
    return true;
  return findNodeFromTail(node);
}

​ 從前面的知識點, 我們知道Condition會維護一個自己的單向鏈表, 將創建的等待節點Node存入鏈表中, 如果Condition調用了signal方法,會將等待隊列中的節點加入到同步隊列中, 然後嘗試獲取鎖, isOnSyncQueue方法的作用是校驗節點是否在同步隊列中

  • 如果return false , 表示節點不在AQS中, 當前節點在等待隊列中,沒有被喚醒去爭搶鎖, 需要等待其它線程調用signal方法來喚醒(注: node.prev == null 表示當前節點已經獲取了鎖資源
  • 如果return true, 意味着節點在AQS隊列中, 需要去競爭鎖資源

具體代碼分析邏輯是

  • node.waitStatus == Node.CONDITION, 節點狀態是CONDITION, 在等待隊列中
  • node.prev == null, 是head節點, 當前節點已經獲取了鎖
  • node.next != null, 說明當前節點的狀態不是CONDITION,存在後繼節點, 則肯定在AQS隊列中
  • findNodeFromTail, 從AQS隊列尾部開始查詢節點是否存在於雙端隊列中, 如果發現說明存在AQS中

reportInterruptAfterWait()

​ 根據interruptMode中斷標識, 來進行中斷上報

  • THROW_IE,則拋出中斷異常
  • REINTERRUPT,則重新響應中斷

acquireQueued()方法在 ReentrantLock原理分析.md 文章中已經講解, 這裏不再分析。

2.3 Condition signal源碼分析

​ 執行Condition.await方法後, 線程會加入等待隊列, 調用Condition.signal方法來激活, 下面是signal方法的入口, 核心方法是doSignal

public final void signal() {
  if (!isHeldExclusively()) // 當前線程是否以獨佔方式持有監視器鎖, 如果不是拋出異常
    throw new IllegalMonitorStateException();
  Node first = firstWaiter; // 等待隊列首節點
  if (first != null)
    doSignal(first); // 存在首節點, 找到第一個CONDITION節點進行signal操作
}

doSignal()

private void doSignal(Node first) {
  do {
    if ( (firstWaiter = first.nextWaiter) == null)
      lastWaiter = null;
    first.nextWaiter = null; //first節點已經處理過了, 將nextWaiter設置爲null
  } while (!transferForSignal(first) &&
           (first = firstWaiter) != null);
}

// 如果節點類型是CONDITION, 加入到AQS隊列中
final boolean transferForSignal(Node node) {
  if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//更新節點狀態爲0, 如果更新失敗說明節點是CANCELLED狀態
    return false;

  Node p = enq(node); // 將當前節點添加到AQS隊列中, 並且返回原隊列的尾節點
  int ws = p.waitStatus;
  // 如果節點是CANCELLED狀態 或者CAS設置節點爲SIGNAL失敗, 喚醒當前線程
  if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    LockSupport.unpark(node.thread);
  return true;//如果node的prev節點是signal狀態, 那麼被阻塞線程的喚醒操作由AQS來完成
}

​ 從Condition隊列首部開始, 查找一個類型是CONDITION的節點, 執行transferForSignal方法, 將節點從Condition等待隊列移入到AQS等待隊列中,同時修改原同步隊列尾節點的狀態。

​ transferForSignal方法通過CAS方式修改節點狀態, 如果成功從CONDITION狀態修改爲默認狀態(0), 將節點添加到AQS隊列中, 然後喚醒這個節點對應的線程, 線程重新執行。

2.4 被阻塞的線程喚醒後的操作

​ 在2.2節講解await方法時checkInterruptWhileWaiting邏輯沒有具體分析, 那麼它的作用是什麼呢 ?分析邏輯: 如果當前線程不在同步隊列中, 那麼該線程會被掛起, 然後檢測線程在等待過程中有沒有被其它線程觸發過中斷請求 。

while (!isOnSyncQueue(node)) {
  LockSupport.park(this);
  if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
    break;
}

​ 執行Condition.signal方法後, 被喚醒的線程會執行if邏輯, 校驗當前線程的中斷情況, 下面我們來分析方法checkInterruptWhileWaiting

private int checkInterruptWhileWaiting(Node node) {
  return Thread.interrupted() ? // 如果線程被中斷了, 執行transferAfterCancelledWait方法判斷是拋出異常(THROW_IE)還是重新中斷(REINTERRUPT)
    (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 
  0;
}

final boolean transferAfterCancelledWait(Node node) {
  // 使用CAS修改節點狀態, 如果成功,對node節點進行入隊操作
  if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    enq(node);
    return true;
  }

  /**
  	* 如果CAS失敗, 判斷當前node節點是否已經在AQS隊列上
  	* 如果不在, yield操作
  	* 如果在, 返回false
  	*/ 
  while (!isOnSyncQueue(node)) 
    Thread.yield();
  return false;
}

​ transferAfterCancelledWait在進行CAS操作的時候可能會失敗, 但是它的失敗可能是:

  • 發生了中斷操作
  • 發生了signal操作

​ 爲了確定具體的操作,需要先將當前節點通過enq方法入隊, 然後返回false給checkInterruptWhileWaiting, 再返回REINTERRUPT(1), 用於後續重新中斷。

​ checkInterruptWhileWaiting方法代表當前線程是否在park的時候被中斷喚醒,

  • 如果爲true, 表示中斷在signal之前, 需要拋出InterruptedException
  • 如果爲false, 表示執行了signal操作, 需要重新中斷

2.5 總結

​ 線程 awaitThread 通過lock.lock()方法獲取鎖成功後調用了 condition.await 方法進入等待隊列,而另一個線程signalThread 通過 lock.lock()方法成功獲取鎖後, 執行condition.signal 或者 signalAll 方法,使得awaitThread線程能夠有機會移入到同步隊列,當其他線程釋放 lock 後,使得awaitThread線程能夠有機會獲取 lock,從而使得awaitThread線程能夠從 await 方法中退出,進行後續操作。如果 awaitThread 獲取 lock 失敗會直接進入到同步隊列

在這裏插入圖片描述

​ 對上面流程圖做下面說明

  • 阻塞,await方法中, 線程釋放鎖資源後, 如果節點不在AQS同步隊列, 則阻塞當前節點, 如果在同步隊列, 則通過自旋方式嘗試獲取鎖
  • 釋放, signal後, 節點會從Condition等待隊列進入AQS同步隊列, 進入正常的鎖獲取流程

三、CountDownLatch

3.1 CountDownLatch作用

​ CountDownLatch是一個工具類, 調用await方法後, 它允許一個或者多個線程一直等待, 直到其它線程操作執行結束, c中核心方法

  • countdown

    每調用一次, state的值就減1, 當state=0時, 會喚醒AQS隊列中的線程

  • await

    調用了await方法, 如果state不爲0, 線程將加入AQS隊列進行等待

​ 之前我們有提到AQS.state這個字段,它在不同併發類中有不同的作用, 比如重入鎖中, state表示重入次數;那CountDownLatch中是什麼意思,它是怎麼指定的呢 ?

//1. 創建對象
CountDownLatch downLatch = new CountDownLatch(3);

//2. CountDownLatch構造函數
public CountDownLatch(int count) {
  if (count < 0) throw new IllegalArgumentException("count < 0");
  this.sync = new Sync(count);
}

//3. Sync構造函數
Sync(int count) {
  setState(count);
}

protected final void setState(int newState) {
  state = newState;
}

​ 從上面創建CountDownLatch的代碼, 可以看出state就是入參的值, 用作計數器, 通過countdown來進行遞減。下面是相關圖示

在這裏插入圖片描述

3.2 CountDownLatch await源碼分析

​ 在進行源碼講解前, 請先看下類繼承圖和CountDownLatch類結構圖,

在這裏插入圖片描述

在這裏插入圖片描述

​ 針對CountDownLatch類,其核心方法是await、countDown,這裏我們先講解await方法

//1. await方法入口
public void await() throws InterruptedException {
  sync.acquireSharedInterruptibly(1);
}

//2. 中斷方式獲取共享鎖
public final void acquireSharedInterruptibly(int arg)
  throws InterruptedException {
  if (Thread.interrupted()) // 中斷了就拋出異常
    throw new InterruptedException();
  if (tryAcquireShared(arg) < 0)
    doAcquireSharedInterruptibly(arg); // wait核心邏輯, state !- 0 , 當前線程需要加入共享鎖隊列
}
  • 首先檢測線程是否被中斷過, 如果被中斷了, 拋出中斷異常

  • 嘗試獲取鎖, 即校驗 state 是否等於 0 , true 返回1,false返回-1

    protected int tryAcquireShared(int acquires) {
      return (getState() == 0) ? 1 : -1;
    }
    
  • 獲取鎖失敗, state !=0 , 將線程加入AQS隊列

doAcquireSharedInterruptibly

注: 在執行鎖匙放時, 如果unparkSuccessor成功, 說明有新的線程佔有了鎖, 代碼會重新回到doAcquireSharedInterruptibly中執行,被喚醒的線程進入下一次循環判斷; state=0時, 會執行setHeadAndPropagate方法

/**
	* Acquires in shared interruptible mode.
	* 以共享中斷方式獲取鎖
  */
private void doAcquireSharedInterruptibly(int arg)
  throws InterruptedException {
  final Node node = addWaiter(Node.SHARED); // 創建SHARED模式的節點, 並加入共享隊列(AQS)末尾
  boolean failed = true;
  try {
    for (;;) {
      final Node p = node.predecessor(); //獲取當前節點的前序節點
      if (p == head) {
        int r = tryAcquireShared(arg); // 如果前序節點是head節點, 嘗試獲取鎖
        if (r >= 0) {
          setHeadAndPropagate(node, r); // 如果state=0, 設置頭節點並繼續喚醒隊列中的其它節點
          p.next = null; // help GC
          failed = false;
          return;
        }
      }
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt()) //阻塞線程
        throw new InterruptedException();
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

​ 針對上面邏輯代碼, 如果state不爲0, 此時有3個線程調用了awat方法, 那麼這三個線程都會加入AQS隊列, 並處於阻塞狀態, 下面是AQS圖解

在這裏插入圖片描述

setHeadAndPropagate

​ 這個方法的作用是把被喚醒的節點,設置成head節點, 然後喚醒隊列中的後續節點, 假如await隊列中有ThreadA -> ThreadB -> ThreadC三個等待線程, 如果ThreadA被喚醒, 且設置爲head節點, 會喚醒ThreadB節點; 如果ThreadB被喚醒且設置爲頭節點, 會喚醒ThreadC節點, 依次類推,請看下面圖解

在這裏插入圖片描述

3.3 CountDownLatch countdown源碼分析

​ 線程調用awati方法阻塞後, 需要調用countDowan方法使得state=0,才能被再次喚醒。

//1. countDown方法入口
public void countDown() {
  sync.releaseShared(1);
}

//2. Sync.releaseShared 方法釋放鎖邏輯
public final boolean releaseShared(int arg) {
  if (tryReleaseShared(arg)) { // 嘗試釋放共享鎖
    doReleaseShared(); // 執行鎖釋放邏輯
    return true;
  }
  return false;
}

//3. 嘗試釋放鎖
protected boolean tryReleaseShared(int releases) {
  // Decrement count; signal when transition to zero
  for (;;) {
    int c = getState();
    if (c == 0)
      return false;
    int nextc = c-1;
    if (compareAndSetState(c, nextc))
      return nextc == 0;
  }
}
  • 當state=0時, 直接返回false, 否則state-1, 通過CAS方式更新state, 再將更新後的state和0比較, 並返回。
  • 如果state=0, 調用doReleaseShared方法進行鎖匙放

doReleaseShared

private void doReleaseShared() {
  for (;;) {
    Node h = head;
    if (h != null && h != tail) {
      int ws = h.waitStatus;
      if (ws == Node.SIGNAL) {
        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
          continue;            // loop to recheck cases
        unparkSuccessor(h); // 喚醒線程
      }
      else if (ws == 0 &&
               !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 如果CAS失敗, 可能是這個時候剛好有個節點入隊, 入隊時, 會將ws修改爲-1
        continue;                // loop on failed CAS
    }
    if (h == head) //說明unparkSuccessor還沒有喚醒線程, 退出循環
      break;
  }
}

​ 注意共享鎖、獨佔鎖釋放的區別, tryRelease的邏輯可以查看 ReentrantLock 原理分析.md

​ 釋放鎖的前部分邏輯和獨佔鎖釋放邏輯一樣, 先判斷頭節點是不是signal狀態, 如果是修改爲默認狀態0, 並喚醒頭節點的下一個節點。

Node.PROPAGATE,標識PROPAGATE狀態的節點, 是共享鎖模式下的節點狀態,處於這個狀態下節點,會對線程的喚醒進行傳播

​ 針對是否退出循環操作,

  • head == h成立, 說明頭節點還沒有被unparkSuccessor喚醒線程, 此時break推出循環
  • head == h不成立, unparkSuccessor喚醒的線程佔有了鎖, 會重新進入下一輪循環, 喚醒下一個線程節點

四、CyclicBarrier

4.1 CyclicBarrier知識

​ CyclicBarrier是循環(Cyclic)柵欄(Barrier),它的作用是讓一組線程到達屏障(Barrier, 也叫同步點)時阻塞, 直到最後一個線程到達屏障, 所有的線程才繼續執行, 類的構造方法如下,參數parties表示柵欄需要攔截的線程數量

CyclicBarrier(int parties)

​ 線程執行await方法時, 是告訴CyclicBarrier當前線程已經到達屏障, 然後線程會被阻塞, 其實現是基於ReentrantLock和Condition組合實現的

注意點

  • CountDownLatch和CyclicBarrier之間的區別, CountDownLatch只能使用一次, CyclicBarrier可以反覆使用

  • 對於計數值 parties,如果沒有足夠的線程調用 CyclicBarrier.await,則所有調用 await 的線程都會被阻塞

  • CyclicBarrier 可以調用 await(timeout, unit),設置超時時間,在設定時間內,如果沒有足夠線程到達,則解除阻塞狀態,繼續執行

  • 通過 reset 重置計數,會使得進入 await 的線程出現 BrokenBarrierException

  • 如果採用是 CyclicBarrier(int parties, Runnable barrierAction) 構 造 方 法 , 執行barrierAction 操作的是最後到達的線程

4.2 CyclicBarrier實踐用例

​ 生產實踐中, 存在數據遷移操作, 在數據量比較大的時候可以創建多線程進行進行分頁批量處理, 下面是邏輯代碼

//main 入口
public class CycliBarrierMain extends Thread {
    @Override
    public void run() {
        System.out.println("執行數據校驗邏輯, 檢查數據量是否正確");
    }

    public static void main(String[] args) {
        //CycliBarrierMain, 其它線程執行完之後最後需要的線程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new CycliBarrierMain()) ;

        new Thread(new DataTransferThread(0, 1000, cyclicBarrier)).start();
        new Thread(new DataTransferThread(1001, 2000, cyclicBarrier)).start();
        new Thread(new DataTransferThread(2001, 3000, cyclicBarrier)).start();
    }
}

// 數據處理線程
public class DataTransferThread extends Thread {
    private CyclicBarrier cyclicBarrier ;
    private int startIndex ;
    private int endIndex ;

    public DataTransferThread(int startIndex, int endIndex, CyclicBarrier cyclicBarrier) {
        this.startIndex = startIndex ;
        this.endIndex = endIndex ;
        this.cyclicBarrier = cyclicBarrier ;
    }

    @Override
    public void run() {
        System.out.println("開始遷移 startIndex=" + startIndex+ " , endIndex=" +endIndex+ " 位置的數據");
        try {
            cyclicBarrier.await(); // 數據遷移完, 執行等待操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

五、Semaphore

5.1 基本作用

​ semaphore可以控制同時訪問的線程個數, 通過acquire獲取一個許可, 如果沒有就等待, 通過release釋放一個許可, 可以用於限流

public Semaphore(int permits) {
  sync = new NonfairSync(permits);
}

​ 參數permits用於設置state的值, 每調用一次acquire, 會執行state=state-1; 調用release的時候,會執行state=state+1; 如果acquire時state=0, 會阻塞當前線程, 等待release釋放鎖資源。

5.2 公平策略、非公平策略

public Semaphore(int permits, boolean fair) {
  sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

​ 參數fair用於決定使用公平策略還是非公平測試, 默認情況使用的是非公平策略

FairSync

static final class FairSync extends Sync {
  private static final long serialVersionUID = 2014338818796000944L;

  FairSync(int permits) {
    super(permits);
  }

  protected int tryAcquireShared(int acquires) {
    for (;;) {
      if (hasQueuedPredecessors())//判斷AQS中是否有線程已經在排隊, 嚴格按照FIFO順序執行
        return -1;
      int available = getState();
      int remaining = available - acquires;
      if (remaining < 0 ||
          compareAndSetState(available, remaining)) // AQS沒有其它等待線程, 執行CAS
        return remaining;
    }
  }
}

NonfairSync

static final class NonfairSync extends Sync {
  private static final long serialVersionUID = -2694183684443567898L;

  NonfairSync(int permits) {
    super(permits);
  }

  protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
  }
}

final int nonfairTryAcquireShared(int acquires) {
  for (;;) {
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 ||
        compareAndSetState(available, remaining))
      return remaining;
  }
}

​ 對比公平策略和非公平策略, 公平策略多了一個hasQueuedPredecessors判斷

源碼分析, 因爲獲取鎖、釋放鎖的邏輯和CountDownLatch一樣基於共享鎖的方式操作的, 代碼邏輯相同, 具體邏輯請看第三節, 這裏不在具體分析。

//獲取鎖
public void acquire() throws InterruptedException {
  sync.acquireSharedInterruptibly(1);
}

//釋放鎖
public void release() {
  sync.releaseShared(1);
}

5.3 實踐用例

​ 這裏以汽車佔用車位示例

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5) ;
        for (int i = 0 ; i < 10 ; i++) {
            new Car(semaphore).start();
        }
    }

    static class Car extends Thread {
        private Semaphore semaphore ;

        public Car(Semaphore semaphore) {
            this.semaphore = semaphore ;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("線程 " + Thread.currentThread().getName() + " 佔用了停車位");
                TimeUnit.SECONDS.sleep(2);
                System.out.println("線程 " + Thread.currentThread().getName() + " 輛車開走了");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章