Java 併發編程解析 | 基於JDK源碼解析Java領域中的併發鎖,我們可以從中學習到什麼內容?

来源:https://www.cnblogs.com/mazhilin/archive/2022/09/17/16702534.html
-Advertisement-
Play Games

蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 在Java領域中, 尤其是在併發編程領域,對於多線程併發執行一直有兩大核心問題:同步和互斥。其中: 互斥(Mutual Exclusion):一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線 ...


蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》

Picture-Navigation

寫在開頭

Picture-Header

在Java領域中, 尤其是在併發編程領域,對於多線程併發執行一直有兩大核心問題:同步和互斥。其中:

  • 互斥(Mutual Exclusion):一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。即就是同一時刻只允許一個線程訪問共用資源的問題。
  • 同步(Synchronization):兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先後次序運行。即就是線程之間如何通信、協作的問題。

針對對於這兩大核心問題,利用管程是能夠解決和實現的,因此可以說,管程是併發編程的萬能鑰匙。

雖然,Java在基於語法層面(synchronized 關鍵字)實現了對管程技術,但是從使用方式和性能上來說,內置鎖(synchronized 關鍵字)的粒度相對過大,不支持超時和中斷等問題。

為了彌補這些問題,從JDK層面對其“重覆造輪子”,在JDK內部對其重新設計和定義,甚至實現了新的特性。

關健術語

Picture-Keyword

本文用到的一些關鍵詞語以及常用術語,主要如下:

  • 信號量(Semaphore): 是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被併發調用,也是作系統用來解決併發中的互斥和同步問題的一種方法。
  • 信號量機制(Semaphores): 用來解決同步/互斥的問題的,它是1965年,荷蘭學者 Dijkstra提出了一種卓有成效的實現進程互斥與同步的方法。
  • 管程(Monitor) : 一般是指管理共用變數以及對共用變數的操作過程,讓它們支持併發的一種機制。

基本概述

在Java領域中,我們可以將鎖大致分為基於Java語法層面(關鍵詞)實現的鎖和基於JDK層面實現的鎖。

在Java領域中,從JDK源碼分析來看,基於JDK層面實現的鎖大致主要可以分為以下4種方式:

  • 基於Lock介面實現的鎖
  • 基於ReadWriteLock介面實現的鎖
  • 基於AQS基礎同步器實現的鎖
  • 基於自定義API操作實現的鎖

從閱讀源碼不難發現,在Java SDK 併發包主要通過AbstractQueuedSynchronizer(AQS)實現多線程同步機制的封裝與定義,而通過Lock 和 Condition 兩個介面來實現管程,其中 Lock 用於解決互斥問題,Condition 用於解決同步問題。

一. 基本理論

在併發編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個線程訪問共用資源;另一個是同步,即線程之間如何通信、協作。

vq67Hs.png

在操作系統中,一般有如果I/O操作時,對於阻塞和非阻塞是從函數調用角度來說的,其中:

  • 阻塞:如果讀寫操作沒有就緒或者完成,則函數一直等待。
  • 非阻塞: 函數立即調用,然後讓應用程式輪詢迴圈。

而同步和非同步則是從“讀寫是主要是由誰完成”的角度來說的,其中:

  • 同步: 讀寫操作主要交給應用程式完成
  • 非同步: 讀寫操作主要由操作系統完成,一般完成之後,回調函數和事件通知應用程式。

其中,信號量機制(Semaphores)是用來解決同步/互斥的問題的,但是信號量(Semaphore)的操作分散在各個進程或線程中,不方便進行管理,因每次需調用P/V(來自荷蘭語 proberen和 verhogen)操作,還可能導致死鎖或破壞互斥請求的問題。

由於PV操作對於解決進程互斥/同步編程複雜,因而在此基礎上提出了與信號量等價的——“管程技術”。

其中,管程(Monitor)當中定義了共用數據結構只能被管程內部定義的函數所修改,所以如果我們想修改管程內部的共用數據結構的話,只能調用管程內部提供的函數來間接的修改這些數據結構。

一般來說,管程(Monitor)和信號量(Semaphore)是等價的,所謂等價指的是用管程能夠實現信號量,也能用信號量實現管程。

在管程的發展歷程上,先後出現過Hasen模型、Hoare模型和MESA模型等三種不同的管程模型,現在正在廣泛使用的是MESA模型。

在MESA模型中,管程中引入了條件變數(Conditional Variable)的概念,而且每個條件變數都對應有一個等待隊列(Wait Queue)。其中,條件變數和等待隊列的作用是解決線程之間的同步問題。

而對於解決線程之間的互斥問題,將共用變數(Shared Variable)及其對共用變數的操作統一封裝起來,一般主要是實現一個線程安全的阻塞隊列(Blocking Queue),將線程不安全的隊列封裝起來,對外提供線程安全的操作方法,例如入隊操作(Enqueue)和出隊操作(Dequeue)。

在Java領域中,對於Java語法層面實現的鎖(synchronized 關鍵字), 其實就是參考了 MESA 模型,並且對 MESA 模型進行了精簡,一般在MESA 模型中,條件變數可以有多個,Java 語言內置的管程(synchronized)里只有一個條件變數。

這就意味著,被synchronized 關鍵字修飾的代碼塊或者直接標記靜態方法以及實例方法,在編譯期會自動生成相關加鎖(lock)和解鎖(unlock)的代碼,即就是monitorenter和monitorexit指令。

對於synchronized 關鍵字來說,主要是在Java HotSpot(TM) VM 虛擬機通過Monitor(監視器)來實現monitorenter和monitorexit指令的。

同時,在Java HotSpot(TM) VM 虛擬機中,每個對象都會有一個監視器,監視器和對象一起創建、銷毀。

監視器相當於一個用來監視這些線程進入的特殊房間,其義務是保證(同一時間)只有一個線程可以訪問被保護的臨界區代碼塊。

本質上,監視器是一種同步工具,也可以說是JVM對管程的同步機制的封裝實現,主要特點是:

  • 同步:監視器所保護的臨界區代碼是互斥地執行的。一個監視器是一個運行許可,任一線程進入臨界區代碼都需要獲得這個許可,離開時把許可歸還。
  • 協作:監視器提供Signal機制,允許正持有許可的線程暫時放棄許可進入阻塞等待狀態,等待其他線程發送Signal去喚醒;其他擁有許可的線程可以發送Signal,喚醒正在阻塞等待的線程,讓它可以重新獲得許可並啟動執行。

在Hotspot虛擬機中,監視器是由C++類ObjectMonitor實現的,ObjectMonitor類定義在ObjectMonitor.hpp文件中,其中:

v7MaNT.png

  • Owner: 指向的線程即為獲得鎖的線程
  • Cxq:競爭隊列(Contention Queue),所有請求鎖的線程首先被放在這個競爭隊列中
  • EntryList:對象實體列表,表示Cxq中那些有資格成為候選資源的線程被移動到EntryList中。
  • WaitSet:類似於等待隊列,某個擁有ObjectMonitor的線程在調用Object.wait()方法之後將被阻塞,然後該線程將被放置在WaitSet鏈表中。

同時,管程與Java中面向對象原則(Object Oriented Principle)也是非常契合的,主要體現在 java.lang.Object類中wait()、notify()、notifyAll() 這三個方法,其中:

  • wait()方法: 阻塞線程並且進入等待隊列
  • notify()方法:隨機地通知等待隊列中的一個線程
  • notifyAll()方法: 通知等待隊列中的所有線程

不難發現,在Java中synchronized 關鍵字及 java.lang.Object類中wait()、notify()、notifyAll() 這三個方法都是管程的組成部分。

由此可見,我們可以得到一個比較通用的併發同步工具基礎模型,大致包含如下幾個內容,其中:

  • 條件變數(Conditional Variable): 利用線程間共用的變數進行同步的一種工作機制
  • 共用變數((Shared Variable)):一般指對象實體對象的成員變數和屬性
  • 阻塞隊列(Blocking Queue):共用變數(Shared Variable)及其對共用變數的操作統一封裝
  • 等待隊列(Wait Queue):每個條件變數都對應有一個等待隊列(Wait Queue),內部需要實現入隊操作(Enqueue)和出隊操作(Dequeue)方法
  • 變數狀態描述機(Synchronization Status):描述條件變數和共用變數之間狀態變化,又可以稱其為同步狀態
  • 工作模式(Operation Mode): 線程資源具有排他性,因此定義獨占模式和共用模式兩種工作模式

綜上所述,條件變數和等待隊列的作用是解決線程之間的同步問題;共用變數與阻塞隊列的作用是解決線程之間的互斥問題。

二.AQS基礎同步器的設計與實現

在Java領域中,同步器是專門為多線程併發設計的同步機制,主要是多線程併發執行時線程之間通過某種共用狀態來實現同步,只有當狀態滿足這種條件時線程才往下執行的一種同步機制。

對於多線程實現實現併發處理機制來說,一直以來,多線程都存在2個問題:

  • 線程之間記憶體共用,需要通過加鎖進行控制,但是加鎖會導致性能下降,同時複雜的加鎖機制也會增加編程編碼難度
  • 過多線程造成線程之間的上下文切換,導致效率低下

因此,在併發編程領域中,一直有一個很重要的設計原則: “ 不要通過記憶體共用來實現通信,而應該通過通信來實現記憶體共用。”

簡單來說,就是儘可能通過消息通信,而不是記憶體共用來實現進程或者線程之間的同步。

其中,同步器是專門為多線程併發設計的同步機制,主要是多線程併發執行時線程之間通過某種共用狀態來實現同步,只有當狀態滿足這種條件時線程才往下執行的一種同步機制。

由於在不同的應用場景中,對於同步器的需求也會有所不同,一般在我們自己去實現和設計一種併發工具的時候,都需會考慮以下幾個問題:

  • 是否支持響應中斷? 如果阻塞狀態的線程能夠響應中斷信號,也就是說當我們給阻塞的線程發送中斷信號的時候,能夠喚醒它,那它就有機會釋放曾經持有的鎖。
  • 是否支持超時?如果線程在一段時間之內沒有獲取到鎖,不是進入阻塞狀態,而是返回一個錯誤,那這個線程也有機會釋放曾經持有的鎖。
  • 是否支持非阻塞地獲取鎖資源 ? 如果嘗試獲取鎖失敗,並不進入阻塞狀態,而是直接返回,那這個線程也有機會釋放曾經持有的鎖。

從閱讀JDK源碼不難發現,主要是採用設計模式中模板模式的原則,JDK將各種同步器中相同的部分抽象封裝成了一個統一的基礎同步器,然後基於基礎同步器為模板通過繼承的方式來實現不同的同步器。

也就是說,在實際開發過程中,除了直接使用JDK實現的同步器,還可以基於這個基礎同步器我們也可以自己自定義實現符合我們業務需求的同步器。

在JDK源碼中,同步器位於java.util.concurrent.locks包下,其基本定義是AbstractQueuedSynchronizer類,即就是我們常說的AQS同步器。

1. 設計思想

一個標準的AQS同步器主要有同步狀態機制,等待隊列,條件隊列,獨占模式,共用模式等五大核心要素組成。

JDK的JUC(java.util.concurrent.)包中提供了各種併發工具,但是大部分同步工具的實現基於AbstractQueuedSynchronizer類實現,其內部結構主要如下:

  • 同步狀態機制(Synchronization Status):主要用於實現鎖(Lock)機制,是指同步狀態,其要求對於狀態的更新必須原子性的
  • 等待隊列(Wait Queue):主要用於存放等待線程獲取到的鎖資源,並且把線程維護到一個Node(節點)裡面和維護一個非阻塞的CHL Node FIFO(先進先出)隊列,主要是採用自旋鎖+CAS操作來保證節點插入和移除的原子性操作。
  • 條件隊列(Condition Queue):用於實現鎖的條件機制,一般主要是指替換“等待-通知”工作機制,主要是通過ConditionObject對象實現Condition介面提供的方法實現。
  • 獨占模式(Exclusive Mode):主要用於實現獨占鎖,主要是基於靜態內部類Node的常量標誌EXCLUSIVE來標識該節點是獨占模式
  • 共用模式(Shared Mode):主要用於實現共用鎖,主要是基於靜態內部類Node的常量標誌SHARED來標識該節點是共用模式

其中,對於AbstractQueuedSynchronizer類的實現原理,我們可以從如下幾個方面來看:


public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691 L;


    protected AbstractQueuedSynchronizer() {}

    /**
     * 等待隊列: head-頭節點
     */
    private transient volatile Node head;

    /**
     * 等待隊列: tail-尾節點
     */
    private transient volatile Node tail;

    /**
     * 同步狀態:32位整數類型,更新同步狀態(state)時必須保證其是原子性的
     */
    private volatile int state;

    /**
     * 自旋鎖消耗超時時間閥值(threshold): threshold < 1000ns時,表示競爭時選擇自旋;threshold > 1000ns時,表示競爭時選擇系統阻塞
     */
    static final long spinForTimeoutThreshold = 1000 L;

    /**
     * CAS原子性操作
     */
    private static final Unsafe unsafe = Unsafe.getUnsafe();

    /**
     * stateOffset
     */
    private static final long stateOffset;

    /**
     * headOffset
     */
    private static final long headOffset;

    /**
     * tailOffset
     */
    private static final long tailOffset;

    /**
     * waitStatusOffset
     */
    private static final long waitStatusOffset;

    /**
     * nextOffset
     */
    private static final long nextOffset;


    static {
        try {
            stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));

        } catch (Exception ex) {
            throw new Error(ex);
        }
    }
		
		    private final boolean compareAndSetHead(Node update)  {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }
		
		    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }
		
		    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }
		
		    private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }
		
		    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

}

[1]. AbstractQueuedSynchronizer類的實現原理是繼承了基於AbstractOwnableSynchronizer類的抽象類,其中主要對AQS同步器的通用特性和方法進行抽象封裝定義,主要包括如下方法:

public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {


    private static final long serialVersionUID = 3737899427754241961 L;


    protected AbstractOwnableSynchronizer() {}

    /**
     *  同步器擁有者
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 設置同步器擁有者:把線程當作參數傳入,指定某個線程為獨享
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    /**
     * 獲取同步器擁有者:獲取指定的某個線程
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}
  • setExclusiveOwnerThread(Thread thread)方法: 把某個線程作為參數傳入,從而設置AQS同步器的所有者,即就是我們設置的某個線程
  • getExclusiveOwnerThread()方法: 獲取當前AQS同步器的所有者,即就是我們指定的某個線程

[2]. 對於同步狀態(state),其類型是32位整數類型,並且是被volatile修飾的,表示在更新同步狀態(state)時必須保證其是原子性的。

[3]. 對於等待隊列的結構,主要是在Node定義了head和tail變數,其中head表示頭部節點,tail表示尾部節點

[4].對於等待隊列的結構提到的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;
			
      /**
       * waitStatus value to indicate the next acquireShared should
       * unconditionally propagate
       */
      static final int PROPAGATE = -3;

      /**
       * Status field, taking on only the values:
       *   SIGNAL:     The successor of this node is (or will soon be)
       *               blocked (via park), so the current node must
       *               unpark its successor when it releases or
       *               cancels. To avoid races, acquire methods must
       *               first indicate they need a signal,
       *               then retry the atomic acquire, and then,
       *               on failure, block.
       *   CANCELLED:  This node is cancelled due to timeout or interrupt.
       *               Nodes never leave this state. In particular,
       *               a thread with cancelled node never again blocks.
       *   CONDITION:  This node is currently on a condition queue.
       *               It will not be used as a sync queue node
       *               until transferred, at which time the status
       *               will be set to 0. (Use of this value here has
       *               nothing to do with the other uses of the
       *               field, but simplifies mechanics.)
       *   PROPAGATE:  A releaseShared should be propagated to other
       *               nodes. This is set (for head node only) in
       *               doReleaseShared to ensure propagation
       *               continues, even if other operations have
       *               since intervened.
       *   0:          None of the above
       *
       *
       * The field is initialized to 0 for normal sync nodes, and
       * CONDITION for condition nodes.  It is modified using CAS
       * (or when possible, unconditional volatile writes).
       */
      volatile int waitStatus;

      /**
       * Link to predecessor node that current node/thread relies on
       */
      volatile Node prev;

      /**
       * Link to the successor node that the current node/thread
       */
      volatile Node next;

      /**
       * The thread that enqueued this node.  Initialized on
       * construction and nulled out after use.
       */
      volatile Thread thread;

      /**
       * Link to next node waiting on condition, or the special
       */
      Node nextWaiter;

      /**
       * Returns true if node is waiting in shared mode.
       */
      final boolean isShared() {
          return nextWaiter == SHARED;
      }


      final Node predecessor() throws NullPointerException {
          Node p = prev;
          if (p == null)
              throw new NullPointerException();
          else
              return p;
      }

      Node() { // Used to establish initial head or SHARED marker
      }

      Node(Thread thread, Node mode) { // Used by addWaiter
          this.nextWaiter = mode;
          this.thread = thread;
      }

      Node(Thread thread, int waitStatus) { // Used by Condition
          this.waitStatus = waitStatus;
          this.thread = thread;
      }
  }

  • 標記Node的工作模式常量標記:主要維護了SHARED和EXCLUSIVE等2個靜態字面常量,其中 SHARED 用於標記Node中是共用模式,EXCLUSIVE:用於標記Node中是獨享模式
  • 標記等待狀態的靜態字面常量標記: 主要維護了0(表示無狀態),SIGNAL(-1,表示後續節點中的線程通過park進入等待,當前節點在釋放和取消時,需要通過unpark解除後後續節點的等待),CANCELLED(1,表示當前節點中的線程因為超時和中斷被取消),CONDITION(-2,表示當前節點在條件隊列中),PROPAGATE(-3,SHARED共用模式的頭節點描述狀態,表示無條件往下傳播)等5個靜態字面常量
  • 維護了一個等待狀態(waitStatus): 主要用於描述等待隊列中節點的狀態,其取值範圍為0(waitStatus=0,表示無狀態),SIGNAL(waitStatus=-1,表示等待信號狀態),CANCELLED(waitStatus=1,表示取消狀態),CONDITION(waitStatus=-2,表示條件狀態),PROPAGATE(waitStatus=-3,表示SHARED共用模式狀態)等5個靜態字面常量,CAS操作時寫入,預設值為0。
  • 維護了Node的2個結構節點變數: 主要是prev和next,其中,prev表示前驅節點,next表示後續節點,表示構成雙向向鏈表,構成了等待隊列的數據結構
  • 維護了一個狀態工作模式標記: 主要是維護了一個nextWaiter,用於表示在等待隊列中當前節點在是共用模式還是獨享模式,而對於條件隊列來說,用於組成單向鏈表結構
  • 維護了一個線程對象變數: 主要用於記錄當前節點中的線程thread

[5].對於自旋鎖消耗超時時間閥值(spinForTimeoutThreshold),主要表示系統依據這個閥值來選擇自旋方式還是系統阻塞。一般假設這個threshold,當 threshold < 1000ns時,表示競爭時選擇自旋;否則,當threshold > 1000ns時,表示競爭時選擇系統阻塞

[6].對於帶有Offset 等變數對應各自的句柄,主要用於執行CAS操作。在JDK1.8版本之前,CAS操作主要通過Unsafe類來說實現;在JDK1.8版本之後,已經開始利用VarHandle來替代Unsafe類操作實現。

[7].對於CAS操作來說,主要提供瞭如下幾個方法:

  • compareAndSetState(int expect, int update)方法:CAS操作原子更新狀態
  • compareAndSetHead(Node update)方法:CAS操作原子更新頭部節點
  • compareAndSetTail(Node expect, Node update)方法:CAS操作原子更新尾部節點
  • compareAndSetWaitStatus(Node node, int expect,int update)方法:CAS操作原子更新等待狀態
  • compareAndSetNext(Node node,Node expect,Node update)方法:CAS操作原子更新後續節點

[8].對於條件隊列(ConditionObject)來說,主要內容如下:

  public class ConditionObject implements Condition, java.io.Serializable {

      private static final long serialVersionUID = 1173984872572414699 L;

      /** First node of condition queue. */
      private transient Node firstWaiter;

      /** Last node of condition queue. */
      private transient Node lastWaiter;

      /** Mode meaning to reinterrupt on exit from wait */
      private static final int REINTERRUPT = 1;

      /** Mode meaning to throw InterruptedException on exit from wait */
      private static final int THROW_IE = -1;

      /**
       * Creates a new {@code ConditionObject} instance.
       */
      public ConditionObject() {}
			
  }

  • 基於Condition的介面實現條件隊列,其核心主要是實現阻塞和喚醒的工作機制
  • 基於Node定義了firstWaiter和lastWaiter變數,其中,firstWaiter表示的是頭節點,lastWaiter是尾節點
  • 還定義了2個字面常量REINTERRUPT和THROW_IE,其中REINTERRUPT=1,描述的是當中斷是退出條件隊列,THROW_IE=-1表示的是發生異常時退出

[8].除此之外,在AQS基礎同步器中,一般可以通過構造方法直接將參數值賦給對應變數,也可以通過變數句柄進行賦值操作:

  • isShared()方法: 用於判斷等待隊列是否為共用模式
  • predecessor()方法: 用於獲取當前節點對應的前驅節點,如果為空,則 throw new NullPointerException();

2. 基本實現

一個標準的AQS同步器最核心底層設計實現是一個非阻塞的CHL Node FIFO(先進先出)隊列數據結構,通過採用自旋鎖+CAS操作的方法來保證原子性操作。

總的來說,一個AQS基礎同步器,底層的數據結構採用的是一個非阻塞的CHL Node FIFO(先進先出)隊列數據結構,而實現的核心演算法則是採用自旋鎖+CAS操作的方法。

首先,對於非阻塞的CHL Node FIFO(先進先出)隊列數據結構,一般來說,FIFO(First In First Out,先進先出)隊列是一個有序列表,屬於抽象型數據類型(Abstract Data Type,ADT),所有的插入和刪除操作都發生在隊首(Front)和隊尾(Rear)兩端,具有先進先出的特性。


    /**
     * 等待隊列: head-頭節點
     */
    private transient volatile Node head;

    /**
     * 等待隊列: tail-尾節點
     */
    private transient volatile Node tail;
		
    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

在AQS同步器的源碼中,主要是通過靜態內部類Node來實現的這個非阻塞的CHL Node FIFO(先進先出)隊列數據結構, 維護了兩個變數head和tail,其中head對應隊首(Front),tail對應隊尾(Rear)。同時,還定義了addWaiter(Node mode)方法來表示入隊操作,其中有個enq(final Node node)方法,主要用於初始化隊列中head和tail的設置。

其次,AQS同步器以CLH鎖為基礎,其中CLH鎖是一種自旋鎖,對於自旋鎖的實現方式來看,主要可以分為普通自旋鎖和自適應自旋鎖,CLH鎖和MCS鎖等4種,其中:

  • 普通自旋鎖:多個線程不斷自旋,不斷嘗試獲取鎖,其不具備公平性和由於要保證CPU和緩存以及主存之間的數據一致性,其開銷較大。
  • 自適應自旋鎖:主要是為解決普通自旋鎖的公平性問題,引入了一個排隊機制,一般稱為排他自旋鎖,其具備公平性,但是沒有解決保證CPU和緩存以及主存之間的數據一致性問題,其開銷較大。
  • CLH鎖:通過一定手段將線程對於某一個共用變數的輪詢競爭轉化為一個線程隊列,且隊列中的線程各自輪詢自己本地變數。
  • MCS鎖:主旨在於解決 CLH鎖的問題,也是基於FIFO隊列,與CLH鎖不同是,只對本地變數自旋,前驅節點負責通知MCS鎖中線程自適結束。

自旋鎖是一種實現同步的方案,屬於一種非阻塞鎖,與常規鎖主要的區別就在於獲取鎖失敗之後的處理方式不同,主要體現在:

  • 一般情況下,常規鎖在獲取鎖失敗之後,會將線程阻塞並適當時重新喚醒
  • 而自旋鎖則是使用自旋來替換阻塞操作,主要是線程會不斷迴圈檢查該鎖是否被釋放,一旦釋放線程便會獲取鎖資源。

從本質上講,自旋是一鐘忙等待狀態,會一直消耗CPU的執行時間。一般情況下,常規互斥鎖適用於持有鎖長時間的情況,自旋鎖適合持有時間短的情況。

其中,對於CLH鎖來說,其核心是為解決同步帶來的花銷問題,Craig,Landim,Hagersten三人發明瞭CLH鎖,其中主要是:

  • 構建一個FIFO(先進先出)隊列,構建時主要通過移動尾部節點tail來實現隊列的排隊,每個想獲得鎖的線程都會創建一個新節點(next)並通過CAS操作原子操作將新節點賦予給tail,當前線程輪詢前一個節點的狀態。
  • 執行完線程後,只需將當前線程對應節點狀態設置為解鎖即可,主要是判斷當前節點是否為尾部節點,如果是直接設置尾部節點設置為空。由於下一個節點一直在輪詢,所以可以獲得鎖。

CLH鎖將眾多線程長時間對資源的競爭,通過有序化這些線程將其轉化為只需要對本地變數檢測。唯一存在競爭的地方就是入隊之前對尾部節點tail 的競爭,相對來說,當前線程對資源的競爭次數減少,這節省了CPU緩存同步的消耗,從而提升了系統性能。

但是同時也有一個問題,CLH鎖雖然解決了大量線程同時操作同一個變數時帶來的開銷問題,如果前驅節點和當前節點在本地主存中不存在,則訪問時間過長,也會引起性能問題。

為了讓CLH鎖更容易實現取消和超時的功能,AQS同步器在設計時進行了改造,主要體現在:節點的結構和節點等待機制。其中:

  • 節點的結構: 主要引入了頭節點和尾節點,分別指向隊列頭部和尾部,對於鎖的相關操作都與其息息相關,並且每個節點都引入了前驅節點和後繼節點。
  • 節點等待機制: 主要在原來的自旋基礎上增加了系統阻塞喚醒,主要體現在 自旋鎖消耗超時時間閥值(threshold): threshold < 1000ns時,表示競爭時選擇自旋;threshold > 1000ns時,表示競爭時選擇系統阻塞。

由此可見,主要是通過前驅節點和後繼節點的引用連接起來形成一個鏈表隊列,其中對於入隊,檢測節點,出隊,判斷超時,取消節點等操作主要如下:

  • 入隊(enqueue): 主要採用一個無限迴圈進行CAS操作,即就是使用自旋方式競爭直到成功。
  • 檢測節點(checkedPrev): 一般在入隊完成後,主要是檢測判斷當前節點的前驅節點是否為頭節點, 一般自旋方式是直接進入迴圈檢測,而系統阻塞方式是當前線程先檢測,其中如果是頭節點併成功獲取鎖,則直接返回,當前線程不阻塞,否則對當前線程進行阻塞。
  • 出隊(dequeue):主要負責喚醒等待隊列中的後繼節點,並且按照條件往下傳播有序執行
  • 判斷超時(checkedTimeout): 隊列中等待鎖的線程可能因為中斷或者超時的情況,當總耗時大於等於自定義耗時就直接返回,即就是
  • 取消節點(cancel): 主要是對於中斷和超時而涉及到取消操作,而且這樣的情況不再參與鎖競爭,即就是一般通過調用compareAndSetNext(Node node, Node expect,Node update)來進行CAS操作。

特別值得註意的是,AQS基礎同步器中主要有等待隊列和條件隊列兩種對列結構,對比便不難發現:等待隊列採用的底層數據結構是雙向鏈表結構,而對於條件隊列則是單向鏈表結構。

最後,AQS同步器中使用了CAS操作,其中CAS(Compare And Swap,比較並交換)操作時一種樂觀鎖策略,主要涉及三個操作數據:記憶體值,預期值,新值,主要是指當且僅當預期值和記憶體值相等時才去修改記憶體值為新值。

一般來說,CAS操作的具體邏輯,主要可以分為三個步驟:

  • 首先,檢查某個記憶體值是否與該線程之前取到值一樣。
  • 其次,如果不一樣,表示此記憶體值已經被別的線程修改,需要捨棄本次操作。
  • 最後,如果時一樣,表示期間沒有線程更改過,則需要用新值執行更新記憶體值。

除此之外,需要註意的是CAS操作具有原子性,主要是由CPU硬體指令來保證,並且通過Java本地介面(Java Native Interface,JNI)調用本地硬體指令實現。

當然,CAS操作避免了悲觀策略獨占對象的 問題,同時提高了併發性能,但是也有以下三個問題:

  • 樂觀策略只能保證一個共用變數的原子操作,如果是多個變數,CAS便不如互斥鎖,主要是CAS操作的局限所致。
  • 長時間迴圈操作可能導致開銷過大。
  • 經典的ABA問題: 主要是檢查某個記憶體值是否與該線程之前取到值一樣,這個判斷邏輯不嚴謹。解決ABA問題的核心在於,引入版本號,每次更新變數值更新版本號。

而在AQS同步器中,為了保證併發實現保證原子性,而且是硬體級別的原子性,一般是通過JNI(Java native interface,Java 本地介面)方式讓Java代碼調用C/C++本地代碼。

通過分析源碼可知,一般使用Unsafe類需要只用關註如下方法即可:


public final class Unsafe {

    private static final Unsafe theUnsafe;

    private static native void registerNatives();

    private Unsafe() {}

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

    public native void unpark(Object var1);

    public native void park(boolean var1, long var2);

}
  • rgisterNatives()方法:是一個靜態 方法,主要用於註冊本地方法
  • 構造函數是private私有化的,一般無法通過構造函數來實例化Unsafe對象
  • getUnsafe()方法:是用來獲取Unsafe對象的,雖然是公有化的,但是如果Java語言開發層面的對進行安全檢查

一般地,由於Unsafe類的操作涉及到硬體底層的操作,JDK對其實例化做了安全校驗,只有受系統信任的代碼才對其實例化,主要是通過類載入器來解析,其實例化方式主要有如下方式:

  • 第一種:直接調用該方法,主要新式是Unsafe.getUnsafe()。但是,對於我們實際開發來說,這種方式無法通過安全校驗行不通,系統會拋出 throw new SecurityException("Unsafe")信息
  • 第二種:通過反射機制繞過安全檢查,主要是修改Unsafe類中theUnsafe欄位的訪問許可權,讓其能被訪問從而達到獲取Unsafe對象的目的

需要註意的是,在Java領域中,對於CAS操作實現,主要有兩點問題:

  • JDK1.8版本之前,CAS操作主要使用Unsafe類來執行底層操作,一般併發和線程操作時,主要用compareAndSwapObject,compareAndSwapInt,compareAndSwapLong等來實現CAS,而對於線程調度主要是park和unpark方法,其主要在sun.misc包下麵。
  • JDK1.8版本之後,JDK1.9的CAS操作主要使用VarHandle類,只是用VarHandle替代了一部分Unsafe類的操作,但是對於新版本中Unsafe,本質上Unsafe類會間接調用jdk.internal.misc包下麵Unsafe類來實現。

3. 具體實現

在Java領域中,AQS同步器利用獨享模式和共用模式來實現同步機制,主要為解決多線併發執行中數據競爭和競爭條件問題。

v7QNMd.png

為解決多線併發執行中數據競爭和競爭條件問題,引入了同步機制,主要是通過控制共用數據和臨界區的訪問,一般比較通用的方式是通過鎖機制來實現。

在Java領域中,JDK對於AQS基礎同步器抽象封裝了鎖的獲取和釋放操作,主要提供了獨享和共用兩種工作模式:

  • 獨享模式(Exclusive Mode) :對應著獨享鎖(Exclusive Lock),表示著對於鎖的獲取和釋放,一次只能至多一個或者只有一個線程把持,其他線程無法獲得並獲得持有,必須等待持有線程釋放鎖。
  • 共用模式(Shared Mode) :對應著共用鎖(Shared Lock),表示著對於鎖的獲取和釋放,一次可以至少一個或者允許多個線程把持,其他線程可以獲得並獲得持有,不用等待持有線程釋放鎖。

其中,AQS基礎同步器對於獨享模式和共用模式的工作模式的基本流程,主要如下:

  • 獲取鎖流程: 先嘗試獲取鎖,如果獲取成功則往下繼續進行,否則把線程維護到等待隊列中,線程可能會掛起。
  • 釋放鎖流程:喚醒等待隊列中的一個或者多個線程去嘗試獲取需要釋放的鎖。

一般地,AQS基礎同步器對於獨享模式和共用模式的封裝和實現,其中:

3.1. 獨享模式的技術實現

[1].獲取鎖操作相關的核心邏輯,主要如下:


public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

    /**
     * 獨占模式:[1].通過acquire獲取鎖操作
     */

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


    /**
     * 獨占模式:[2].通過tryAcquire嘗試獲取鎖操作
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }



    /**
     * 獨占模式:[3].通過addWaiter入隊操作
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    /**
     * 獨占模式:[4].通過acquireQueued檢測節點
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    /**
     * 獨占模式:[5].通過cancelAcquire取消鎖獲取
     */
    private void cancelAcquire(Node node) {

        if (node == null)
            return;

        node.thread = null;


        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;


        Node predNext = pred.next;


        node.waitStatus = Node.CANCELLED;


        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {

            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                    (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

            node.next = node; 
        }
    }


}

[2]. 釋放鎖操作相關的核心邏輯,主要如下:


public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {


    /**
     * 獨占模式:[1].通過release釋放鎖操作
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    /**
     * 獨占模式:[2].通過tryRelease嘗試釋放鎖操作
     */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

    /**
     * 獨占模式:[3].通過unparkSuccessor喚醒後繼節點
     */
    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);


        Node s = node.next;
        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);
    }

}

由此可見,對於獨占模式的鎖獲取和釋放,主要是依據acquire和release等方法來實現。

3.1. 共用模式的技術實現

[1].獲取鎖操作相關的核心邏輯,主要如下:


public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

    /**
     * 共用模式:[1].通過acquireShared獲取鎖操作
     */

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

    /**
     * 共用模式:[2].通過tryAcquireShared嘗試獲取鎖操作
     */
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

    /**
     * 共用模式:[3].通過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);
        }
    }

}

[2].釋放鎖操作相關的核心邏輯,主要如下:


public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

    /**
     * 共用模式:[1].通過releaseShared釋放鎖操作
     */

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    /**
     * 共用模式:[2].通過tryReleaseShared嘗試釋放鎖操作
     */


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

    /**
     * 共用模式:[3].通過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;
                    unparkSuccessor(h);
                } else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;
            }
            if (h == head)
                break;
        }
    }
		
}

由此可見,對於共用模式的鎖獲取和釋放,主要是依據acquireShared和releaseShared等方法來實現。

v7QaqI.png

綜上所述,不難看出,AQS同步器設計思想是通過繼承的方式提供一個模板,其核心原理是管理一個共用狀態,通過對狀態的控制來實現不同的控制。

二. LockSupport的設計與實現

在Java領域中,LockSupport主要從線程資源角度為同步器和鎖提供基本線程阻塞和喚醒原語,是“等待-通知”工作機制的實現。

vLYc3n.png

一般來說,當一個線程(Thread)只要參與鎖競爭時,其經歷的主要流程有:

  • 一旦當前線程進行鎖競爭時,線程都會嘗試獲取鎖,根據獲取鎖的情況進行後續處理。
  • 如果獲取鎖失敗,則會創建節點插入到隊列的尾部,會二次嘗試重新獲取鎖,並不會阻塞當前線程。
  • 如果獲取鎖成功,則直接返回,否則會將節點設置為待運行狀態(SIGNAL)。
  • 最後對當前線程進行阻塞,當前驅節點運行完成後會喚醒後繼節點。

在Java領域中,對於線程的阻塞和喚醒,也許我們最早在學習面向對象原則的時候,一般都使用java.lang.Object類中wait()、notify()、notifyAll() 這三個方法,可以用它們幫助我們實現等待-通知”工作機制。

同時,在講解AQS基礎同步器的實現時,提到說CAS操作的核心是使用Unsafe類來執行底層操作,對於線程調度主要是park和unpark方法,但是一般的Java語言層 main開發對其調用又有安全檢查的限制。

但是,在AQS基礎同步的的阻塞和喚醒操作咋在獲取鎖餓的鎖操作中需要使用,一般地:

  • 如果獲取不到鎖的當前線程在進入排到隊列之後需要阻塞當前線程。
  • 並且,排到隊列中前驅節點運行完成後,需要負責喚醒後繼節點。

於是,在AQS基礎同步器的設計與實現中,封裝一個專門用於實現“等待-通知”工作機制的LockSupport類。

對於LockSupport類,主要是為同步器和鎖提供基本線程阻塞和喚醒原語,AQS同步器和鎖都是使用它來阻塞和喚醒線程。主要源碼如下:


public class LockSupport {

    /**
     *  阻塞操作:利用park阻塞某個線程(指定參數)
     */
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0 L);
        setBlocker(t, null);
    }

    /**
     *  阻塞操作:利用park阻塞某個線程(無指定參數)
     */
    public static void park() {
        UNSAFE.park(false, 0 L);
    }


    /**
     *  阻塞操作:根據nanos許可,利用parkNanos阻塞某個線程
     */
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

    /**
     *  阻塞操作:根據nanos許可,利用parkNanos阻塞某個線程,但是指定阻塞對象
     */
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }

    /**
     *  阻塞操作:根據deadline最大等待時間,利用parkUntil阻塞某個線程
     */
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

    /**
     *  阻塞操作:根據deadline最大等待時間,利用parkUntil阻塞某個線程,需要指定阻塞對象
     */
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }


    /**
     *  喚醒操作:利用unpark喚醒某個線程
     */
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    /**
     *  設置阻塞器: 指定線程和對象
     */
    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

    /**
     *  獲取阻塞器中線程對象
     */
    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }

    /**
     *  獲取阻塞器中線程對象
     */
    static final int nextSecondarySeed() {
        int r;
        Thread t = Thread.currentThread();
        if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
            r ^= r << 13; // xorshift
            r ^= r >>> 17;
            r ^= r << 5;
        } else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
            r = 1; // avoid zero
        UNSAFE.putInt(t, SECONDARY, r);
        return r;
    }

    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;

    private static final long parkBlockerOffset;

    private static final long SEED;

    private static final long PROBE;

    private static final long SECONDARY;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class <? > tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

}

  • 設計思想: 相比用java.lang.Object類中的wait/notify方式,其LockSupport類更關註線程本身,解耦了線程之間的同步。
  • 實現原理: 主要還是使用Unsafe類來執行底層操作,主要是間接調用是park和unpark本地方法
  • 阻塞操作涉及方法: 一般以park開頭的方法來阻塞線程操作,大致可以分為自定義阻塞對象參數和非自定義阻塞對象參數等阻塞方法
  • 喚醒線程操作: 主要通過unpark方法來對當前線程設置可用,相對於喚醒操作

三. Condition介面的設計與實現

在Java領域中,Condition介面是用來實現管程技術,其中 Condition用於解決同步問題。

vLNCoF.png

相對於LockSupport的設計與實現來說,Condition介面只是在JDK層面對於阻塞和喚醒提供了一個模板的定義,是AQS基礎同步器中條件隊列的定義,而ConditionObject是在AQS基礎同步器具體實現。

對於Condition介面而言,是提供了可替代wait/notify機制的條件隊列模式,其中:


public interface Condition {

    /**
     * 條件隊列模式:等待await操作
     */
    void await() throws InterruptedException;

    /**
     * 條件隊列模式:等待awaitUninterruptibly操作,可中斷模式
     */
    void awaitUninterruptibly();

    /**
     * 條件隊列模式:等待awaitNanos操作,可超時模式
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    /**
     * 條件隊列模式:等待await操作,可超時模式
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;

    /**
     *條件隊列模式:等待awaitUntil操作,可超時模式
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;

    /**
     * 條件隊列模式:通知signal操作
     */
    void signal();

    /**
     * 條件隊列模式:通知signalAll操作
     */
    void signalAll();
}
  • 定義了關於“等待(wait)”機制的相關實現:而以await開頭的所有方法都是關於等待機制的定義。
  • 定義了關於“通知(signal)”機制的相關實現: signal()方法和signalAll()方法,其中 signal()方法是隨機地通知等待隊列中的一個線程,而signalAll()方法是通知等待隊列中的所有線程。

四. Lock介面的設計與實現

在Java領域中,Lock介面是用來實現管程技術,其中 Lock 用於解決互斥問題。

vqfsu8.png

Lock介面位於java.util.concurrent.locks包中,是JUC顯式鎖的一個抽象,Lock介面的主要抽象方法:

public interface Lock {

    /**
     * Lock介面-獲取鎖
     */
    void lock();

    /**
     * Lock介面-獲取鎖(可中斷)
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * Lock介面-嘗試獲取鎖
     *
     */
    boolean tryLock();

    /**
     *Lock介面-嘗試獲取鎖(支持超時)
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     *Lock介面-釋放鎖
     *
     */
    void unlock();

    /**
     *  Lock介面-設置條件變數
     */
    Condition newCondition();
}

  • 獲取鎖: lock()
  • 釋放鎖:unlock()
  • 條件變數: Condition

在JDK中,對於Lock介面的具體實現主要是ReentrantLock類,其中:


public class ReentrantLock implements Lock, java.io.Serializable {

    private static final long serialVersionUID = 7373984872572414699 L;

    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;


    /**
     * 構造鎖的非公平模式(預設模式)
     */
    public ReentrantLock() {
            sync = new NonfairSync();
        }
				
    /**
     * 構造鎖的公平和非公平模式(可選公平或者非公平)
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    /**
     * Lock介面-實現嘗試獲取鎖
     */
    public void lock() {
        sync.lock();
    }

    /**
     * Lock介面-實現嘗試獲取鎖
     */
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * Lock介面-實現嘗試獲取鎖
     */
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    /**
     * Lock介面-實現嘗試獲取鎖
     */
    public boolean tryLock(long timeout, TimeUnit unit)
    throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    /**
     * Lock介面-釋放鎖
     */
    public void unlock() {
        sync.release(1);
    }

    /**
     * Lock介面-創建條件變數
     */
    public Condition newCondition() {
        return sync.newCondition();
    }


}

  • 包含了一個同步器Sync,主要是基於AbstractQueuedSynchronizer實現,同時基於Sync類還實現FairSync類和NonfairSync類,其中FairSync類對應著公平模式,NonfairSync類對應非公平模式。
  • 實現Lock介面設計和定義的相關方法,可以設定其鎖是公平和非公的,預設是非公平模式的。

相對於Java內置鎖,Java SDK 併發包里的 Lock介面主要區別有能夠響應中斷、支持超時和非阻塞地獲取鎖等三個特性。

五. ReadWriteLock介面的設計與實現

在Java領域中,ReadWriteLock介面主要是基於Lock介面來封裝了ReadLock鎖和WriteLock鎖等2種鎖的實現方法,其中ReadLock鎖是讀鎖的介面定義,WriteLock鎖是寫鎖的介面定義。

vqffCn.png

ReadWriteLock介面的內部實現,主要是基於Lock介面來獲取ReadLock鎖和WriteLock鎖的,其具體代碼如下:


public interface ReadWriteLock {
    /**
     * ReadWriteLock介面-基於Lock介面實現ReadLock
     */
    Lock readLock();

    /**
     * ReadWriteLock介面-基於Lock介面實現WriteLock
     */
    Lock writeLock();
}

  • readLock()方法: 獲取讀鎖,主要是基於基於Lock介面來定義,表示著其具體實現類都會基於AQS基礎同步器實現
  • writeLock()方法:獲取寫鎖,主要是基於基於Lock介面來定義,表示著其具體實現類都會基於AQS基礎同步器實現

在JDK中,對於ReadWriteLock介面的具體實現主要是ReentrantReadWriteLock類,其中:


public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {

    private static final long serialVersionUID = -6992448646407690164 L;

    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;

    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

    /** Performs all synchronization mechanics */
    final Sync sync;


    /**
     * 同步器-基於AbstractQueuedSynchronizer實現
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        //.....

        abstract boolean readerShouldBlock();

        abstract boolean writerShouldBlock();
    }

    /**
     * 同步器-非公平模式
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037 L;

        final boolean writerShouldBlock() {
            return false;
        }

        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

    /**
     * 同步器-公平模式
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451 L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }


    /**
     * 構造鎖的非公平模式(預設預設)
     */
    public ReentrantReadWriteLock() {
        this(false);
    }

    /**
     * 構造鎖的公平和非公平模式(可選公平或者非公平)
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    /**
     * ReadWriteLock介面-基於Lock具體實現的writeLock()
     */
    public ReentrantReadWriteLock.WriteLock writeLock() {
        return writerLock;
    }

    /**
     * ReadWriteLock介面-基於Lock具體實現的readLock()
     */
    public ReentrantReadWriteLock.ReadLock readLock() {
        return readerLock;
    }

    /**
     * ReadWriteLock介面-ReadLock內置類
     */
    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -59924486464076

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

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是i.MXRT中FlexSPI外設lookupTable里配置訪問行列混合定址Memory的參數值。 關於 FlexSPI 外設的 lookupTable,痞子衡之前寫過一篇非常詳細的文章 《從頭開始認識i.MXRT啟動頭FDCB里的lo ...
  • 超級熱鍵可通過簡單編程 —— 自動化完成複雜操作,提升效率。 ▶ 快速上手 本教程需要一個很小的開源軟體 ImTip ( 體積 639 KB ), 請右鍵點開 ImTip 托盤菜單,然後點擊「管理超級熱鍵」: 然後將熱鍵配置改為如下代碼,並且勾選「啟用超級熱鍵」,再點擊「保存」按鈕使熱鍵生效。 // ...
  • 2022-09-17 NoSQL(not only SQL)的介紹: 是一種非關係型資料庫。 NoSQL常用的產品種類: Redis Mongodb Hbase hadoop Redis常用的場景: (1)可用於緩衝,即記憶體裡面,速度快。 (2)用於社交類軟體,一個小例子:抖音點贊,如果是放在關係型 ...
  • MySQL的通用日誌: 用來記錄對資料庫的通用操作,包括錯誤的sql語句等信息。 通用日誌可以保存在:file(預設值)或 table(mysql.general_log表) mysql通用日誌的設置: general_log=ON|OFF 是否啟用通用日誌 general_log_file=HOS ...
  • 網路配置 cd /etc/sysconfig/network-scripts/ ls vi ifcfg-ens33 修改網路配置 TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no BOOTPROTO=static #設置靜態 IPADDR=192.168 ...
  • @(目錄標題) 為什麼需要響應式網頁 點擊打開視頻講解更加詳細 隨著網頁數量和質量的上升,以及設備種類和數量的增加,不同設備查看不同網頁導致的縮放問題、排版問題等一系列前端問題越發明顯。 想要解決他們,我們可以為不同類型的設備編寫不同樣式的代碼,做不同版本的測試。 然而這樣做有兩個弊端: 工作量過大 ...
  • 自定義模塊 為什麼要模塊?模塊化源代碼能給我們帶來什麼好處? 試想一個巨無霸網購平臺,在沒有模塊化的情況下,如果出現bug,程式員就要在幾百萬行代碼里調試,導致後期維護成本上升,為瞭解決問題,模塊化按功能切分,把大問題轉換成小問題,讓每個模塊獨立運營,通過介面對外開放,讓程式統一調用,降低程式出錯的 ...
  • 在講到new關鍵字的執行過程之前,有幾個關於構造函數和對象之間的關係和構造函數的特點需要重點掌握: ###1.構造函數和對象的關係和區別: 構造函數:構造函數抽象了對象的公共的屬性和方法,封裝到了函數裡面,它泛指的是某一大類; 對象:通過new函數創建對象,也稱為對象藉助構造函數完成的對象實例化。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...