(十二)JDK源碼分析之常用併發工具類

  • CountDownLatch

    • 功能
      1.需要等待多個線程都完成某個任務,然後喚醒主線程繼續執行;
      2.當兩個線程時,主線程需要等待子線程完成任務到某個進度,然後喚醒主線程繼續執行。

    • 使用
      1.創建CountDownLatch對象,給一個初始值n,可以理解爲進度數字n
      2.執行CountDownLatch的await方法,主線程會被阻塞,直到CountDownLatch設置的n等於0
      3.在你認爲需要調用CountDownLatch的countDown方法地方調用,這個時候其實n就會減一
      4.如果你調用了countDown方法n次,那麼主線程會從阻塞狀態中被喚醒,主線程繼續執行

    • 原理
      1.基於AQS實現的,利用AQS的同步功能
      2.CountDownLatch的await方法其實就是將當前線程加入到阻塞隊列,然後掛起當前線程
      3.CountDownLatch的countDown方法其實就是嘗試喚醒阻塞隊列中的線程,當n=0的時候,主線程對應在阻 塞隊 列的節點會被喚醒(n>0時會喚醒失敗,但是每次都會嘗試喚醒),喚醒後,主線程就繼續執行。

    • 代碼

      public void await() throws InterruptedException {
              sync.acquireSharedInterruptibly(1);
      }
      
      private void doAcquireSharedInterruptibly(int arg)
          throws InterruptedException {
          //加入到阻塞隊列
          final Node node = addWaiter(Node.SHARED);
          boolean failed = true;
          try {
              for (;;) {
                  final Node p = node.predecessor();
                  if (p == head) {
                  	//status值大於0,那麼r會返回-1
                      int r = tryAcquireShared(arg);
                      if (r >= 0) {
                          setHeadAndPropagate(node, r);
                          p.next = null; // help GC
                          failed = false;
                          return;
                      }
                  }
                  //正常情況喚醒失敗,線程掛起
                  if (shouldParkAfterFailedAcquire(p, node) &&
                      parkAndCheckInterrupt())
                      throw new InterruptedException();
              }
          } finally {
              if (failed)
                  cancelAcquire(node);
          }
      }
      
      	public void countDown() {
      	        sync.releaseShared(1);
      	}
      
      	 public final boolean releaseShared(int arg) {
      	       //tryReleaseShared方法只有在status==0的情況下才會返回true,否則只對status-1,
      	       //然後返回false
      	        if (tryReleaseShared(arg)) {
      	        	//doReleaseShared喚醒阻塞隊列的線程,也就是頭結點的next
      	            doReleaseShared();
      	            return true;
      	        }
      	        return false;
      	 }
      	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))
      	                    continue;                // loop on failed CAS
      	            }
      	            if (h == head)                   // loop if head changed
      	                break;
      	        }
      	    }
      
  • CyclicBarrier

    • 功能
      1.需要等待多個子線程都完成某個任務,最後一個子線程或者主線程喚醒所有子線程;
      2.喚醒子線程們後,所有線程繼續執行,或者也可以指定一個任務,在突破阻塞點的時候執行
      3.可以重複使用,一次阻塞點被解除後可以繼續使用

    • 使用
      1.創建CyclicBarrier對象,給一個初始值n,也就是多少線程在阻塞點後繼續執行
      2.在你的程序中調用CyclicBarrier的await方法,表示到達阻塞點,這個時候當前線程阻塞,這個方法既用於同步阻塞使用,也用於解除阻塞使用,當調用await是最後一個阻塞點的時候,會解除所謂的柵欄,這一點也是和CountDownLatch最大的區別,在實現方式上。

    • 原理
      1.基於AQS實現的,利用AQS的同步功能
      2.CyclicBarrier的await方法會將當前線程加入到等待隊列,然後掛起當前線程,等到最後一個await方法調用的時候,會喚醒(將條件隊列轉移到阻塞隊列)所有條件隊列的節點,最後喚醒阻塞隊列的第一個節點開始執行。

    • 代碼

      private int dowait(boolean timed, long nanos)
              throws InterruptedException, BrokenBarrierException,
                     TimeoutException {
              final ReentrantLock lock = this.lock;
              lock.lock();
              try {
                  final Generation g = generation;
      
                  if (g.broken)
                      throw new BrokenBarrierException();
      
                  if (Thread.interrupted()) {
                      breakBarrier();
                      throw new InterruptedException();
                  }
      
      			//每次調用await方法都會將count減一
                  int index = --count;
                  //如果是最後一個await被調用,那麼執行下面的邏輯
                  if (index == 0) {  // tripped
                      boolean ranAction = false;
                      try {
                      	//執行CyclicBarrier創建時指定的任務
                          final Runnable command = barrierCommand;
                          if (command != null)
                              command.run();
                          ranAction = true;
                          //將所有等待隊列的節點加入到阻塞隊列中,重新初始化,可以重複使用目的
                          nextGeneration();
                          return 0;
                      } finally {
                          if (!ranAction)
                              breakBarrier();
                      }
                  }
      
                  // loop until tripped, broken, interrupted, or timed out
                  //如果不是最後一個await,那麼count>0,執行下面邏輯
                  for (;;) {
                      try {
                          if (!timed)
                          	//將當前線程加入到等待隊列中,當前線程掛起
                              trip.await();
                          else if (nanos > 0L)
                              nanos = trip.awaitNanos(nanos);
                      } catch (InterruptedException ie) {
                          if (g == generation && ! g.broken) {
                              breakBarrier();
                              throw ie;
                          } else {
                              // We're about to finish waiting even if we had not
                              // been interrupted, so this interrupt is deemed to
                              // "belong" to subsequent execution.
                              Thread.currentThread().interrupt();
                          }
                      }
      
                      if (g.broken)
                          throw new BrokenBarrierException();
      
                      if (g != generation)
                          return index;
      
                      if (timed && nanos <= 0L) {
                          breakBarrier();
                          throw new TimeoutException();
                      }
                  }
              } finally {
              	//喚醒阻塞隊列,讓阻塞隊列中任務開始執行
                  lock.unlock();
              }
          }
      
  • 總結
    使用上:

    • CountDownLatch定義的阻塞點,也就是那個n,並不代表多少個線程,只是代表countDown的執行次數
    • CyclicBarrier定義的阻塞點,也就是那個n,必須是要在n個線程中調用await方法,因爲await會阻塞當前線程,所以調用多次也是徒勞,必須要n個線程調用才能消耗掉這個n

    原理上

    • CountDownLatch是直接基於阻塞隊列的,一個CountDownLatch就是阻塞隊列的一個節點,和n沒關係
    • CyclicBarrier是基於條件隊列的,n是多少就有多少個節點體現在等待隊列中,和n有關係。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章