JDK源碼之AQS源碼剖析

来源:http://www.cnblogs.com/showing/archive/2017/05/17/6858410.html
-Advertisement-
Play Games

除特別註明外,本站所有文章均為原創,轉載請註明地址 AbstractQueuedSynchronizer(AQS)是JDK中實現併發編程的核心,平時我們工作中經常用到的ReentrantLock,CountDownLatch等都是基於它來實現的。 AQS類中維護了一個雙向鏈表(FIFO隊列), 如下 ...


     除特別註明外,本站所有文章均為原創,轉載請註明地址   

 

     AbstractQueuedSynchronizer(AQS)是JDK中實現併發編程的核心,平時我們工作中經常用到的ReentrantLock,CountDownLatch等都是基於它來實現的。

     AQS類中維護了一個雙向鏈表(FIFO隊列), 如下圖所示:

    

    隊列中的每個元素都用一個Node表示,我們可以看到,Node類中有幾個靜態常量表示的狀態:

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;

        volatile Node next;
volatile Thread thread; Node nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { } Node(Thread thread, Node mode) { this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } }

  此外,AQS中通過一個state的volatile變數表示同步狀態。

  那麼AQS是如何通過隊列實現鎖操作的呢?

一.獲取鎖操作

   下麵的是AQS中執行獲取鎖的代碼:

public final void acquire(int arg) {
/**通過tryAcquire獲取鎖,如果成功獲取到鎖直接終止(selfInterrupt),否則將當前線程插入隊列
* 這裡的Node.EXCLUSIVE表示創建一個獨占模式的節點
*/
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

  然而實際上,AQS中並沒有實現上面的tryAcquire(arg)方法,具體獲取鎖的操作需要由其子類比如ReentrantLock中的Sync實現:

 

protected final boolean tryAcquire(int acquires) {
//取到當前線程
final Thread current = Thread.currentThread();
//獲取到state值(前文提到)
int c = getState();
//state為0標識當前沒有線程占有鎖
//如果隊列中前面沒有元素(因為是公平鎖的原因,非公平鎖中不進行判斷,如果state為0直接獲取到鎖),CAS修改當前值
if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
//標識當前線程成功獲取鎖 setExclusiveOwnerThread(current);
return true; } }
//state不為0,且占有鎖的線程是當前線程(這裡涉及到一個可重入鎖的概念)
else if (current == getExclusiveOwnerThread()) {
//增加重入次數
int nextc = c + acquires;
//如果次數值溢出,拋出異常
if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; }
//如果鎖已經被其它線程占用,獲取鎖失敗
return false; }

       上面的代碼註釋中提到了可重入鎖的概念,可重入鎖又叫遞歸鎖,簡單來講就是已經獲取到鎖的線程還可以再次獲取到同一個鎖,我們通常使用的syschronized操作,ReentrantLock都屬於可重入鎖。自旋鎖則不屬於可重入鎖。

 下麵我們再看一下如果tryAcquire失敗,AQS是如何處理的:

private Node addWaiter(Node mode) {
//創建一個隊列的Node Node node
= new Node(Thread.currentThread(), mode); //獲取當前隊列尾部 Node pred = tail; if (pred != null) {
//CAS操作嘗試插入Node到等待隊列,這裡只嘗試一次 node.prev
= pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } }
//如果添加失敗,enq這裡會做自旋操作,知道插入成功。 enq(node);
return node; }
//自旋操作添加元素到隊列尾部
private Node enq(final Node node) { for (;;) {
//獲取尾節點 Node t
= tail;
//如果尾節點為空,說明當前隊列是空,需要初始化隊列
if (t == null) {
//初始化當前隊列 if (compareAndSetHead(new Node())) tail = head; } else {
//通過CAS操作插入Node,設置Node為隊列的尾節點,並返回Node node.prev
= t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
/**
* 如果插入的節點前面是head,嘗試獲取鎖,
*/
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false;
//自旋操作
for (;;) {
//獲取當前插入節點的前置節點
final Node p = node.predecessor();
//前置節點是head,嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
//設置head為當前節點,表示獲取鎖成功 setHead(node); p.next
= null; // help GC failed = false; return interrupted; }
//是否掛起當前線程,如果是,則掛起線程
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }

    上面的代碼有些複雜,這裡解釋一下,之前的addWaiter代碼已經將node加入了等待隊列,所以這裡需要讓節點隊列中掛起,等待喚醒。隊列的head節點代表的是當前占有鎖的節點,首先判斷插入的node的前置節點是否是head,如果是,嘗試獲取鎖(tryAcquire),如果獲取成功則將head設置為當前節點;如果獲取失敗需要判斷是否掛起當前線程。

 
/**
* 判斷是否可以掛起當前線程
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//ws為node前置節點的狀態
int ws = pred.waitStatus; if (ws == Node.SIGNAL)        //如果前置節點狀態為SIGNAL,當前節點可以掛起 return true; if (ws > 0) {

//通過迴圈跳過所有的CANCELLED節點,找到一個正常的節點,將當前節點排在它後面

//GC會將這些CANCELLED節點回收 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else {
//將前置節點的狀態修改為SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }

 

//通過LockSupport掛起線程,等待喚醒
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

 

 

二.釋放鎖操作

    有了獲取鎖的基礎,再來看釋放鎖的源碼就比較容易了,下麵的代碼執行的是AQS中釋放鎖的操作:

//釋放鎖的操作
public
final boolean release(int arg)
//嘗試釋放鎖,這裡tryRelease同樣由子類實現,如果失敗直接返回false
if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

 下麵的代碼是嘗試釋放鎖的操作:

 protected final boolean tryRelease(int releases) {
//獲取state值,釋放一定值
int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false;
//如果差是0,表示鎖已經完全釋放
if (c == 0) { free = true;
//下麵設置為null表示當前沒有線程占用鎖 setExclusiveOwnerThread(
null); }
//如果c不是0表示鎖還沒有完全釋放,修改state值 setState(c);
return free; }

 釋放鎖後,還需要喚醒隊列中的一個後繼節點:

private void unparkSuccessor(Node node) {
        //將當前節點的狀態修改為0
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //從隊列里找出下一個需要喚醒的節點
//首先是直接後繼 Node s
= node.next;
//如果直接後繼為空或者它的waitStatus大於0(已經放棄獲取鎖了),我們就遍歷整個隊列,
//獲取第一個需要喚醒的節點
if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null)
//將節點喚醒 LockSupport.unpark(s.thread); }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 平臺簡介 Jeesz是一個分散式的框架,提供項目模塊化、服務化、熱插拔的思想,高度封裝安全性的Java EE快速開發平臺。 Jeesz本身集成Dubbo服務管控、Zookeeper註冊中心、Redis分散式緩存技術、FastDFS分散式文件系統、ActiveMQ非同步消息中間件、Nginx負載均衡等分 ...
  • 使用JPA中@Query 註解實現update 操作,代碼如下: @Transactional@Modifying(clearAutomatically = true)@Query(value = "update info p set p.status =?1 where p.id = ?2",na ...
  • PHP編程中經常需要用到伺服器的一些資料,特把$_SERVER的詳細參數整理下: $_SERVER['PHP_SELF'] 當前正在執行腳本的文件名,與 document root相關。 $_SERVER['argv'] 傳遞給該腳本的參數。 $_SERVER['argc'] 包含傳遞給程式的命令行 ...
  • java中的數據類型,可分為兩類:1.基本數據類型,也稱原始數據類型。byte,short,char,int,long,float,double,boolean 他們之間的比較,應用雙等號(==),比較的是他們的值。 2.複合數據類型(類) 當他們用(==)進行比較的時候,比較的是他們在記憶體中的存放 ...
  • ////////////////////////////////////////////////////////////////// 記性不好的可以收藏下: 1,下拉框: 稍微解釋一下: 1.select[@name='country'] option[@selected] 表示具有name 屬性, ...
  • SRC_DIR := src/ INC_DIR := include/ OBJ_DIR := build/ DEP_DIR := build/ EXE_DIR := build/ SRC := $(notdir $(shell ls $(SRC_DIR)*.cpp)) OBJ := $(patsub... ...
  • Golang 支持在一個平臺下生成另一個平臺可執行程式的交叉編譯功能。 Mac下編譯Linux, Windows平臺的64位可執行程式: Linux下編譯Mac, Windows平臺的64位可執行程式: Windows下編譯Mac, Linux平臺的64位可執行程式: GOOS:目標可執行程式運行操 ...
  • 學習Java的同學註意了!!! 學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:618528494 我們一起學Java! Java編程思想,Java學習必讀經典,不管是初學者還是大牛都值得一讀,這裡總結書中的重點知識,這些知識不僅經常出現在各大知名公司的筆試面試過程 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...