1. 程式人生 > >Java併發之AQS原始碼分析(二)

Java併發之AQS原始碼分析(二)

我在Java併發之AQS原始碼分析(一)這篇文章中,從原始碼的角度深度剖析了 AQS 獨佔鎖模式下的獲取鎖與釋放鎖的邏輯,如果你把這部分搞明白了,再看共享鎖的實現原理,思路就會清晰很多。下面我們繼續從原始碼中窺探共享鎖的實現原理。

共享鎖

獲取鎖

public final void acquireShared(int arg) {
  // 嘗試獲取共享鎖,小於0表示獲取失敗
  if (tryAcquireShared(arg) < 0)
    // 執行獲取鎖失敗的邏輯
    doAcquireShared(arg);
}

這裡的 tryAcquireShared 方法是留給實現方去實現獲取鎖的具體邏輯的,我們主要看 doAcquireShared 方法的實現邏輯:

private void doAcquireShared(int arg) {
  // 新增共享鎖型別節點到佇列中
  final Node node = addWaiter(Node.SHARED);
  boolean failed = true;
  try {
    boolean interrupted = false;
    for (;;) {
      final Node p = node.predecessor();
      if (p == head) {
        // 再次嘗試獲取共享鎖
        int r = tryAcquireShared(arg);
        // 如果在這裡成功獲取共享鎖,會進入共享鎖喚醒邏輯
        if (r >= 0) {
          // 共享鎖喚醒邏輯
          setHeadAndPropagate(node, r);
          p.next = null; // help GC
          if (interrupted)
            selfInterrupt();
          failed = false;
          return;
        }
      }
      // 與獨佔鎖相同的掛起邏輯
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
        interrupted = true;
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

看到上面的程式碼,是不是有一種熟悉的感覺,同樣是採用了自旋機制,線上程掛起之前,不斷地迴圈嘗試獲取鎖,不同的是,一旦獲取共享鎖,會呼叫 setHeadAndPropagate 方法同時喚醒後繼節點,實現共享模式,下面是喚醒後繼節點程式碼邏輯:

private void setHeadAndPropagate(Node node, int propagate) {
  // 頭節點
  Node h = head; 
  // 設定當前節點為新的頭節點
  // 這裡不需要加鎖操作,因為獲取共享鎖後,會從FIFO佇列中依次喚醒佇列,並不會產生併發安全問題
  setHead(node);
  if (propagate > 0 || h == null || h.waitStatus < 0 ||
      (h = head) == null || h.waitStatus < 0) {
    // 後繼節點
    Node s = node.next;
    // 如果後繼節點為空或者後繼節點為共享型別,則進行喚醒後繼節點
    // 這裡後繼節點為空意思是隻剩下當前頭節點了
    if (s == null || s.isShared())
      doReleaseShared();
  }
}

該方法主要做了兩個重要的步驟:

  1. 將當前節點設定為新的頭節點,這點很重要,這意味著當前節點的前置節點(舊頭節點)已經獲取共享鎖了,從佇列中去除;
  2. 呼叫 doReleaseShared 方法,它會呼叫 unparkSuccessor 方法喚醒後繼節點。

釋放鎖

public final boolean releaseShared(int arg) {
  // 由使用者自行實現釋放鎖條件
  if (tryReleaseShared(arg)) {
    // 執行釋放鎖
    doReleaseShared();
    return true;
  }
  return false;
}

下面是釋放鎖邏輯:

private void doReleaseShared() {
  for (;;) {
    // 從頭節點開始執行喚醒操作
    // 這裡需要注意,如果從setHeadAndPropagate方法呼叫該方法,那麼這裡的head是新的頭節點
    Node h = head;
    if (h != null && h != tail) {
      int ws = h.waitStatus;
      //表示後繼節點需要被喚醒
      if (ws == Node.SIGNAL) {
        // 初始化節點狀態
        //這裡需要CAS原子操作,因為setHeadAndPropagate和releaseShared這兩個方法都會頂用doReleaseShared,避免多次unpark喚醒操作
        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
          // 如果初始化節點狀態失敗,繼續迴圈執行
          continue;            // loop to recheck cases
        // 執行喚醒操作
        unparkSuccessor(h);
      }
      //如果後繼節點暫時不需要喚醒,那麼當前頭節點狀態更新為PROPAGATE,確保後續可以傳遞給後繼節點
      else if (ws == 0 &&
               !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
        continue;                // loop on failed CAS
    }
    // 如果在喚醒的過程中頭節點沒有更改,退出迴圈
    // 這裡防止其它執行緒又設定了頭節點,說明其它執行緒獲取了共享鎖,會繼續迴圈操作
    if (h == head)                   // loop if head changed
      break;
  }
}

共享鎖的釋放鎖邏輯比獨佔鎖的釋放鎖邏輯稍微複雜,原因是共享鎖需要釋放佇列中所有共享型別的節點,因此需要迴圈操作,由於釋放鎖過程中會涉及多個地方修改節點狀態,此時需要 CAS 原子操作來併發安全。

獲取共享鎖流程圖:

總結

更獨佔鎖相比,從流程圖也可看出,共享鎖的主要特徵是當有一個執行緒獲取到鎖之後,那麼它就會依次喚醒等待佇列中可以跟它共享的節點,當然這些節點也是共享鎖型別。

微信公眾號   
 
 </div> 
 <div class=

相關推薦

Java併發AQS原始碼分析

我在Java併發之AQS原始碼分析(一)這篇文章中,從原始碼的角度深度剖析了 AQS 獨佔鎖模式下的獲取鎖與釋放鎖的邏輯,如果你把

併發程式設計——ThreadPoolExecutor原始碼分析

前言 在上一篇中,我們分析了ThreadPoolExecutor中關鍵變數ctl,這篇我們繼續來看ThreadPoolExecutor中的建構函式及其引數。其中引數的相關解釋來源於原始碼中的相關注釋。 建構函式 我們可以看到ThreadPoolExecutor

Java安全Commons Collections1分析

# Java安全之Commons Collections1分析(二) ## 0x00 前言 續上篇文,繼續除錯cc鏈。在上篇文章除錯的cc鏈其實並不是一個完整的鏈。只是使用了幾個方法的的互相呼叫彈出一個計算器。 [Java安全之Commons Collections1分析(一)](https://www

Java併發ThreadPoolExecutor原始碼解析

Worker 先前,筆者講解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在這個方法中工作執行緒可能建立成功,也可能建立失敗,具體視執行緒池的邊界條件,以及當前記憶體情況而定。 那麼,如果執行緒池當前的狀態,是允許建立Worke

Java多執行緒ReentrantLock實現原理和原始碼分析

章節概覽、 1、ReentrantLock概述 ReentrantLock字面含義是可重入的互斥鎖,實現了和synchronize關鍵字一樣的獨佔鎖功能。但是ReentrantLock使用的是自旋鎖,通過CAS硬體原語指令實現的輕量級的鎖,不會引起上下文切換

Glide原始碼分析——從用法來看load&into方法

上一篇,我們分析了with方法,文章連結: https://blog.csdn.net/qq_36391075/article/details/82833260 在with方法中,進行了Glide的初始化,建立了RequesManger,並且綁定了生命週期,最終返回了一個Reques

zigbee ZStack-2.5.1a原始碼分析 無線接收控制LED

本文描述ZStack-2.5.1a 模板及無線接收移植相關內容。 main HAL_BOARD_INIT // HAL_TURN_OFF_LED1 InitBoard HalDriverInit HalAdcInit

tornado原始碼分析iostream

在事件驅動模型中,所有任務都是以某個事件的回撥函式的方式新增至事件迴圈中的,如:HTTPServer要從socket中讀取客戶端傳送的request訊息,就必須將該socket新增至ioloop中,並設定回掉函式,在回掉函式中從socket中讀取資料,並且檢查request訊息是否全部接收到了,如果

java集合----ArrayList原始碼分析基於jdk1.8

一、ArrayList 1、ArrayList是什麼: ArrayList就是動態陣列,用MSDN中的說法,就是Array的複雜版本,它提供了動態的增加和減少元素,實現了ICollection和IList介面,靈活的設定陣列的大小等好處,實現了Randomaccess介面,支援快速隨

java集合----HashMap原始碼分析基於JDK1.7與1.8

一、什麼是HashMap 百度百科這樣解釋: 簡而言之,HashMap儲存的是鍵值對(key和value),通過key對映到value,具有很快的訪問速度。HashMap是非執行緒安全的,也就是說在多執行緒併發環境下會出現問題(死迴圈) 二、內部實現 (1)結構 HashM

Docker原始碼分析Docker Client

一、建立Docker Client     Docker是一個client/server的架構,通過二進位制檔案docker建立Docker客戶端將請求型別與引數傳送給Docker Server,Docker Server具體執行命令呼叫。 Docker Client執行流

Java容器類原始碼-Vector的最全的原始碼分析

    三、原始碼解讀 1. 繼承、實現 extends:AbstractList<E> implements:List<E>, RandomAccess, Cloneable, java.io.Serializable 2.

SpringMVC原始碼分析請求如何轉發到對應的Controller

        在前一篇對DispatcherServlet的分析中,初略的過了下請求是如何處理的,本文將重點分析,HandlerMapping與HandlerAdapter是如何工作的          在web容器啟動的過程中,會初初始化一系列SpringMVC所需

死磕 java同步系列ReentrantLock原始碼解析——條件鎖

問題 (1)條件鎖是什麼? (2)條件鎖適用於什麼場景? (3)條件鎖的await()是在其它執行緒signal()的時候喚醒的嗎? 簡介 條件鎖,是指在獲取鎖之後發現當前業務場景自己無法處理,而需要等待某個條件的出現才可以繼續處理時使用的一種鎖。 比如,在阻塞佇列中,當佇列中沒有元素的時候是無法彈出一個元素

Java並發AQS原理分析

jpg 子類 ole success ces || pro 同步 無法 我們說的AQS就是AbstractQueuedSynchronizer,他在java.util.concurrent.locks包下,這個類是Java並發的一個核心類。第一次知道有這個類是在看可重入鎖R

小白的java學習路 “ 選擇結構

pre ring 問題 -s stat 適合 static 之路 span switch 選擇結構: 為什麽使用switch選擇結構: switch選擇結構可以更好地解決等值判斷問題。 switch選擇結構的四個關鍵字: switch case default

Flume NG原始碼分析支援執行時動態修改配置的配置模組

在上一篇中講了Flume NG配置模組基本的介面的類,PropertiesConfigurationProvider提供了基於properties配置檔案的靜態配置的能力,這篇細說一下PollingPropertiesFileConfigurationProvider提供的執行時動態修改配置並生效的

GCC原始碼分析——前端

原文連結:http://blog.csdn.net/sonicling/article/details/6706152   從這一篇開始,我們將從原始碼的角度來分析GCC如何完成對C語言原始檔的處理。GCC的內部構架在GCC Internals(搜“gccint.pdf”,或者見[

YOLOv2原始碼分析

文章全部YOLOv2原始碼分析 接著上一講沒有講完的make_convolutional_layer函式 0x01 make_convolutional_layer //make_convolutional_laye

兄弟連區塊鏈入門教程eth原始碼分析p2p-udp.go原始碼分析

ping方法與pending的處理,之前談到了pending是等待一個reply。 這裡通過程式碼來分析是如何實現等待reply的。pending方法把pending結構體傳送給addpending. 然後等待訊息的處理和接收。 // ping sends a ping message to the giv