蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》
寫在開頭
在併發編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個線程訪問共享資源;另一個是同步,即線程之間如何通信、協作。
主要原因是,對於多線程實現實現併發,一直以來,多線程都存在2個問題:
- 線程之間內存共享,需要通過加鎖進行控制,但是加鎖會導致性能下降,同時複雜的加鎖機制也會增加編程編碼難度
- 過多線程造成線程之間的上下文切換,導致效率低下
因此,在併發編程領域中,一直有一個很重要的設計原則: “ 不要通過內存共享來實現通信,而應該通過通信來實現內存共享。”
簡單來說,就是儘可能通過消息通信,而不是內存共享來實現進程或者線程之間的同步。
關健術語
本文用到的一些關鍵詞語以及常用術語,主要如下:
- 併發(Concurrent): 在操作系統中,是指一個時間段中有幾個程序都處於已啓動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行。
- 並行(Parallel): 當系統有一個以上CPU時,當一個CPU執行一個進程時,另一個CPU可以執行另一個進程,兩個進程互不搶佔CPU資源,可以同時進行。
- 信號量(Semaphore): 是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被併發調用,也是作系統用來解決併發中的互斥和同步問題的一種方法。
- 信號量機制(Semaphores): 用來解決同步/互斥的問題的,它是1965年,荷蘭學者 Dijkstra提出了一種卓有成效的實現進程互斥與同步的方法。
- 管程(Monitor) : 一般是指管理共享變量以及對共享變量的操作過程,讓它們支持併發的一種機制。
- 互斥(Mutual Exclusion):一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。即就是同一時刻只允許一個線程訪問共享資源的問題。
- 同步(Synchronization):兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先後次序運行。即就是線程之間如何通信、協作的問題。
- 對象池(Object Pool): 指的是一次性創建出 N 個對象,之後所有的線程重複利用這 N 個對象,當然對象在被釋放前,也是不允許其他線程使用的, 一般指保存實例對象的容器。
基本概述
在Java領域中,我們可以將鎖大致分爲基於Java語法層面(關鍵詞)實現的鎖和基於JDK層面實現的鎖。
在Java領域中, 尤其是在併發編程領域,對於多線程併發執行一直有兩大核心問題:同步和互斥。其中:
- 互斥(Mutual Exclusion):一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。即就是同一時刻只允許一個線程訪問共享資源的問題。
- 同步(Synchronization):兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先後次序運行。即就是線程之間如何通信、協作的問題。
針對對於這兩大核心問題,利用管程是能夠解決和實現的,因此可以說,管程是併發編程的萬能鑰匙。
雖然,Java在基於語法層面(synchronized 關鍵字)實現了對管程技術,但是從使用方式和性能上來說,內置鎖(synchronized 關鍵字)的粒度相對過大,不支持超時和中斷等問題。
爲了彌補這些問題,從JDK層面對其“重複造輪子”,在JDK內部對其重新設計和定義,甚至實現了新的特性。
在Java領域中,從JDK源碼分析來看,基於JDK層面實現的鎖大致主要可以分爲以下4種方式:
- 基於Lock接口實現的鎖:JDK1.5版本提供的ReentrantLock類
- 基於ReadWriteLock接口實現的鎖:JDK1.5版本提供的ReentrantReadWriteLock類
- 基於AQS基礎同步器實現的鎖:JDK1.5版本提供的併發相關的同步器Semaphore,CyclicBarrier以及CountDownLatch等
- 基於自定義API操作實現的鎖:JDK1.8版本中提供的StampedLock類
從閱讀源碼不難發現,在Java SDK 併發包主要通過AbstractQueuedSynchronizer(AQS)實現多線程同步機制的封裝與定義,而通過Lock 和 Condition 兩個接口來實現管程,其中 Lock 用於解決互斥問題,Condition 用於解決同步問題。
一.AQS基礎同步器基本理論
在Java領域中,同步器是專門爲多線程併發設計的同步機制,主要是多線程併發執行時線程之間通過某種共享狀態來實現同步,只有當狀態滿足這種條件時線程才往下執行的一種同步機制。
一個標準的AQS同步器主要有同步狀態機制,等待隊列,條件隊列,獨佔模式,共享模式等五大核心要素組成。
在Java領域中,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來標識該節點是共享模式
我們可以得到一個比較通用的併發同步工具基礎模型,大致包含如下幾個內容,其中:
- 條件變量(Conditional Variable): 利用線程間共享的變量進行同步的一種工作機制
- 共享變量((Shared Variable)):一般指對象實體對象的成員變量和屬性
- 阻塞隊列(Blocking Queue):共享變量(Shared Variable)及其對共享變量的操作統一封裝
- 等待隊列(Wait Queue):每個條件變量都對應有一個等待隊列(Wait Queue),內部需要實現入隊操作(Enqueue)和出隊操作(Dequeue)方法
- 變量狀態描述機(Synchronization Status):描述條件變量和共享變量之間狀態變化,又可以稱其爲同步狀態
- 工作模式(Operation Mode): 線程資源具有排他性,因此定義獨佔模式和共享模式兩種工作模式
綜上所述,條件變量和等待隊列的作用是解決線程之間的同步問題;共享變量與阻塞隊列的作用是解決線程之間的互斥問題。
二. JDK顯式鎖統一概念模型
在併發編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個線程訪問共享資源;另一個是同步,即線程之間如何通信、協作。
綜合Java領域中的併發鎖的各種實現與應用分析來看,一把鎖或者一種鎖,基本上都會包含以下幾個方面:
- 鎖的同步器工作機制:主要是考慮共享模式還是獨享模式,是否支持超時機制,以及是否支持超時機制?
- 鎖的同步器工作模式:主要是基於AQS基礎同步器封裝內部同步器,是否考慮公平/非公平模式?
- 鎖的狀態變量機制: 主要鎖的狀態設置,是否共享狀態變量?
- 鎖的隊列封裝定義:主要是指等待隊列和條件隊列,是否需要條件隊列或者等待隊列定義?
- 鎖的底層實現操作: 主要是指底層CL鎖和CAS操作,是否需要考慮自旋鎖或者CAS操作實例對象方法?
- 鎖的組合實現新鎖: 主要是基於獨佔鎖和共享鎖,是否考慮對應API自定義操作實現?
綜上所述,大致可以根據上述這些方向,我們便可以清楚🉐️知道Java領域中各種鎖實現的基本理論時和實現思想。
六.CountDownLatch(閉鎖)的設計與實現
在Java領域中,CountDownLatch(閉鎖)是針對於Java多線程併發控制中倒計數器的具體數量,主要是採用遞減計數方式的倒計數器思想和基於AQS基礎同步器來實現的一種同步器工具類。
CountDownLatch(閉鎖)是Java多線程併發中最常見的一種同步器,從鎖的性質上來看,屬於共享鎖,其功能相當於一個多線程環境下的倒數門閂。
CountDownLatch通過定義一個倒計數器,在併發環境下由線程進行遞減1操作,當計數值變爲0之後,被await方法阻塞的線程將會喚醒。
通過CountDownLatch可以實現線程間的計數同步。
1. 設計思想
一般來說,通過定義一個倒計數器,爲了讓某個線程或者多個線程在某個運行節點上等待N個條件都滿足後,才讓所有的線程繼續往下執行,其中倒計數器的數量則爲N,每滿足一個條件,倒計數器就依次逐漸遞減1,直到N-1=0的時,所有等待的線程才往下繼續執行。
CountDownLatch類最早是在JDK1.5版本提供的,從設計思想上來看,主要包括倒計數器的同步器,控制阻塞等待的方法,倒計數器的遞減操作方法等3個核心要素。其中:
- 倒計數器的同步器:基於AQS基礎抽象隊列同步器封裝內置實現一個靜態的內置同步類,主要用於設置倒計數器的初始值以及定製AQS基礎同步器的獲取和釋放共享鎖。
- 倒計數器的初始值: 一般在構建CountDownLatch類時指定,表示的是需要等待條件的個數,即就是倒計數器的具體的資源數量Source(N)。
- 控制線程阻塞等待的方法:定義一個控制線程阻塞等待的方法,當倒計數器的具體的資源數量 Source(N)>0時,調用方法使其線程進入阻塞等待狀態。
- 倒計數器的遞減操作方法:定義一個倒計數器的遞減操作方法,調用方法就會把倒計數器遞減1,當倒計數器的具體的資源數量 Source(N)-1=0時,所有等待的線程才往下繼續執行。
簡單來說,CountDownLatch主要是讓某個線程或者多個線程,等待其他線程完成某件事情或者某個任務結束之後才能繼續執行。
2. 基本實現
在CountDownLatch類的JDK1.8版本中,對於CountDownLatch的基本實現如下:
public class CountDownLatch {
private final Sync sync;
/**
* CountDownLatch鎖-構造一個倒計數器
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* CountDownLatch鎖-基於AQS定義支持同步器實現
*/
private static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860 L;
//......其他方法代碼
}
/**
* CountDownLatch鎖-線程等待方法
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* CountDownLatch鎖-倒計數器遞減操作
*/
public void countDown() {
sync.releaseShared(1);
}
//... 其他代碼
}
- 倒計數器同步器:基於AQS基礎定義支持同步器實現一個靜態私有化的同步器Sync類,其中定義了獲取和釋放共享鎖的兩個方法
- 線程等待方法:主要是提供了一個await()方法,其本質是調用的是AQS基礎同步器中的acquireSharedInterruptibly(int arg)方法,否則throws InterruptedException異常
- 倒計數器遞減操作方法: 主要是提供了一個countDown()方法,其本質是調用的是AQS基礎同步器中的releaseShared(int arg) 方法
2.1 基於AQS同步器封裝靜態內部Sync抽象類
/**
* CountDownLatch鎖-基於AQS同步器封裝一個內部的同步器
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
/**
* CountDownLatch鎖-獲取共享鎖方法
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* CountDownLatch鎖-釋放共享鎖方法
*/
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;
}
}
}
- 實現方式: 主要基於AQS封裝的內部靜態抽象Sync同步類實現,使用的AQS的共享模式
- 主要方法: 主要定製適配提供了tryAcquireShared()和tryReleaseShared()方法,即就是tryAcquireShared()用於獲取共享鎖,tryReleaseShared()方法用於釋放共享鎖,其中:
- 獲取共享鎖tryAcquireShared()方法:首先獲取狀態變量status,這裏是指倒計數器中的數量,當status=0時,返回值=1,表示獲取鎖成功;否則,status !=0 時,返回值=-1,表示獲取共享鎖失敗進行入隊。
- 釋放共享鎖tryReleaseShared()方法: 通過自旋來實現遞減操作,其中會獲取狀態變量status,將其遞減1後使用compareAndSetState(c, nextc)方法通過CAS修改狀態值
- 鎖獲取方式: 主要是利用getCount()來獲取倒計數器中的數量,同時還可以利用構造方法指導一個倒計數器中的數量。
3. 具體實現
public class CountDownLatch {
private final Sync sync;
/**
* CountDownLatch鎖-基於AQS基礎同步器實現一個內部同步器
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374 L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
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;
}
}
}
/**
* CountDownLatch鎖-構造一個倒計數器
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* CountDownLatch鎖-基於AQS定義支持同步器實現
*/
private static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860 L;
//......其他方法代碼
}
/**
* CountDownLatch鎖-線程等待方法
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* CountDownLatch鎖-返回當前計數器
*/
public long getCount() {
return sync.getCount();
}
/**
* CountDownLatch鎖-線程等待方法(支持超時機制)
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* CountDownLatch鎖-倒計數器遞減操作
*/
public void countDown() {
sync.releaseShared(1);
}
}
- 倒計數初始值:通過構造方法CountDownLatch(int count)指定一個倒計數器的初始值,其必須大於0,否則會throw new IllegalArgumentException("count < 0")
- 線程等待方法: 主要提供了await() 方法和await(long timeout, TimeUnit unit)方法,其中:
- 無參數await() 方法: 一般默認的方法,其本質是調用AQS同步器中的acquireSharedInterruptibly()方法,主要表示支持中斷機制
- 有參數await(long timeout, TimeUnit unit)方法: 是用於實現超時機制,其本質是調用AQS同步器中的tryAcquireSharedNanos(int arg, long nanosTimeout)方法
- 倒計數遞減操作方法:主要是countDown() 方法, 其本質是調用AQS同步器中的releaseShared(int arg) 方法,核心實現是AQS基礎同步器的doReleaseShared方法。
- 其他方法: 主要是getCount() 方法,用來獲取倒計數個數,其本質是調用AQS同步器中getCount()方法,來獲取狀態變量
綜上所述,從一定意義上講,CountDownLatch是一種共享鎖,屬於AQS基礎抽象隊列同步器中共享模式孵化的產物,沒有支持公平模式與非公平模式的實現。
七.CyclicBarrier(循環屏障)的設計與實現
在Java領域中,CyclicBarrier(循環屏障)是針對於Java多線程併發控制中倒計數器的線程數量,主要是採用遞減計數方式的倒計數器思想和基於AQS基礎同步器實現的ReentrantLock鎖來實現的一種同步器工具類。
CyclicBarrier(循環屏障)是Java中通過對線程預定義設置一個屏障,只有當到達屏障的線程數量到達指定的最大屏障時,屏障纔會讓這些線程通過執行。
從一定意義上來講,這裏的屏障本質上還是一個倒計數器,倒計數器的最大限度支持的數量就是我們爲線程設置屏障大小,其工作原理與CountDownLatch(閉鎖)類似,都是通過讓線程阻塞等待時,倒計數器執行遞減1運算。
但是與CountDownLatch不同是,CyclicBarrier(循環屏障)是基於ReentrantLock(可重入鎖)來實現的,更準確的說,CyclicBarrier是對ReentrantLock的應用實例。
1. 設計思想
一般來說,通過定義一個倒計數器,爲了讓某個線程或者多個線程在某個運行節點上約束N個線程,需要讓指定數量的線程共同到達某一個節點之後,這些線程纔會一起被執行。
CyclicBarrier(循環屏障)最早是在JDK1.5版本中提供的,從設計思想上來看,主要包括倒計數器的最大屏障,控制阻塞等待的方法,倒計數器的遞減操作方法,和觸發點線程任務等4個核心要素。其中:
- 倒計數器的同步器: 主要基於ReentrantLock來實現控制線程對象,其本質還是基於AQS基礎同步器實現。
- 倒計數器的最大屏障數量:一般是在構建CyclicBarrier(循環屏障)對象是預定義設置,表示需要在某個運行節點上約束的線程數量。
- 控制線程阻塞等待的方法:定義一個方法,使得實現阻塞線程讓其進入等待狀態。
- 倒計數器的遞減操作方法:定義一個方法,使得讓倒計數器進行遞減1運算,直到達到屏障時,等待的線程才繼續執行。
- 觸發點線程任務:一般指的是當指定數量的線程達到設置的屏障時,纔會去觸發執行的任務。
簡單來說,CyclicBarrier(循環屏障)是讓多個線程互相等待,直到達到一個同步的運行節點。再繼續一起執行。
2. 基本實現
在CyclicBarrier類的JDK1.8版本中,對於CountDownLatch的基本實現如下:
public class CyclicBarrier {
/** CyclicBarrier鎖—屏障lock實體 */
private final ReentrantLock lock = new ReentrantLock();
/** CyclicBarrier鎖—屏障條件隊列 */
private final Condition trip = lock.newCondition();
/** CyclicBarrier鎖—屏障最大值 */
private final int parties;
/** CyclicBarrier鎖—屏障觸發線程任務目標 */
private final Runnable barrierCommand;
/** CyclicBarrier鎖—當前計數器的最大值屏障實例 */
private Generation generation = new Generation();
/** CyclicBarrier鎖—當前計數器的最大值屏障實例 */
private int count;
/** CyclicBarrier鎖—屏障實例 */
private static class Generation {
boolean broken = false;
}
/** CyclicBarrier鎖—構造一個屏障實例(不帶觸發任務的) */
public CyclicBarrier(int parties) {
this(parties, null);
}
/** CyclicBarrier鎖—構造一個屏障實例(帶觸發任務的) */
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
/** CyclicBarrier鎖—無參數構造一個等待方法(默認模式) */
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
/** CyclicBarrier鎖—有參數構造一個等待方法(支持超時機制) */
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
/** CyclicBarrier鎖—更新狀態變量 */
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
/** CyclicBarrier鎖—阻塞屏障 */
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
//...其他代碼
}
- 預定義設置屏障最大值: 主要是通過變量parties來實現預定義設置屏障最大值
- 設置當前屏障數量:主要是通過變量count來實現
- 控制線程的對象實例: 主要是通過ReentrantLock和Condition來控制線程間通信
- 觸發目標任務對象: 主要是通過Runable來定義barrierCommand變量
- 提供了兩個構造方法:都需要預定義指定屏障最大值parties,其中一個需要傳入barrierAction觸發點任務
- 線程阻塞等待方法:主要提供了2個await()方法,其中:
- 無參數await()方法:默認處理方式,不支持超時機制,其核心處理邏輯在dowait(boolean timed, long nanos)方法中實現
- 有參數await()方法:指定參數處理,支持超時機制,其核心處理邏輯在dowait(boolean timed, long nanos)方法中實現
- 屏障設置關健方法:主要是breakBarrier() 來實現,其中:
- 通知到達屏障的所有線程:主要是通過Condition中的signalAll()來通知屏障中所有線程已經滿足條件
- 屏障設置:默認預定義設置屏障最大值與設置當前屏障數相同,主要設置count = parties
- 更新屏障狀態:主要是通過generation.broken = true來實現
- 更新屏障的狀態:主要是提供了nextGeneration() 方法,表示已經到達預定義設置屏障最大值,其中:
- 通知到達屏障的所有線程:主要是通過Condition中的signalAll()來通知屏障中所有線程已經滿足條件
- 準備下一輪屏障設置:意味着預定義設置屏障最大值與設置當前屏障數相同,主要設置count = parties
- 重置屏障狀態:主要是通過generation = new Generation()來實現
�一般來說,假設我們允許控制的最大線程數量爲N,預定義設置屏障最大值爲Parties(N), 當前屏障的線程數量爲Current(N) ,當前屏障中的等待線程數量爲Waiting(N),那麼我們會得到一個計算公式:
2.1 構造Generation屏障實例標記
private static class Generation {
boolean broken = false;
}
主要是構造了一個靜態私有化的Generation類,其中定義了一個broken變量來作爲屏障標記,默認初始值爲false,表示還沒達到屏障最大值。
2.1 線程阻塞等待覈心dowait方法
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// [1].實例化構建ReentrantLock的對象
final ReentrantLock lock = this.lock;
// [2].通過lock()獲取鎖或者說加鎖操作
lock.lock();
try {
// [3].實例化構建Generation屏障實例對象
final Generation g = generation;
// [4].判斷Generation屏障實例標記狀態
if (g.broken)
throw new BrokenBarrierException();
// [5].判斷Thread是包含中斷標誌位
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// [6].對倒計數器的屏障數量遞減1運算
int index = --count;
// [7].依據結果index == 0表示當前指定的線程數量到達屏障最大值,需要觸發Runnable任務
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 進行下一輪屏障設置
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// [7].自旋操作
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 {
// 是否發生線程中斷
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
// 如果等待時間超過指定超時時間,throw new TimeoutException
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 最後釋放鎖操作
lock.unlock();
}
}
- 加鎖操作: 實例化構建ReentrantLock的對象,通過lock()方法進行加鎖操作
- 判斷屏障實例標記狀態:實例化構建Generation實例標記,判斷屏障實例標記狀態是否一致,如果不一致則throw new BrokenBarrierException();
- 判斷當前線程是否被中斷: 判斷Thread是包含中斷標誌位,如果中斷throw new InterruptedException()並調用breakBarrier()重新設置屏障
- 屏障倒計數器遞減運算:對倒計數器的屏障數量遞減1運算,即就是對當前倒計數器的當前值減去1
- 觸發節點線程任務: 當前倒計數器的當前值爲0時,需要觸發Runnable任務,並調用nextGeneration方法開啓下一輪操作;否則,當前倒計數器的當前值不爲0時,調用awaitNanos(nanos)方法進入等待狀態
- 自旋操作判斷超時: 如果使用了超時參數,調用awaitNanos(nanos)方法進入等待狀態,其中如果發生中斷則調用Thread.currentThread().interrupt()設置中斷標記。如果等待時間> 指定超時時間,拋出throw new TimeoutException()異常
- 釋放鎖: 通過unlock()方法進行解鎖操作,並釋放鎖
3. 具體實現
在CyclicBarrier類的JDK1.8版本中,對於CyclicBarrier的具體實現如下:
public class CyclicBarrier {
/** CyclicBarrier鎖—屏障lock實體 */
private final ReentrantLock lock = new ReentrantLock();
/** CyclicBarrier鎖—屏障條件隊列 */
private final Condition trip = lock.newCondition();
/** CyclicBarrier鎖—屏障最大值 */
private final int parties;
/** CyclicBarrier鎖—屏障觸發線程任務目標 */
private final Runnable barrierCommand;
/** CyclicBarrier鎖—當前計數器的最大值屏障實例 */
private Generation generation = new Generation();
/** CyclicBarrier鎖—當前計數器的最大值屏障實例 */
private int count;
/** CyclicBarrier鎖—屏障實例 */
private static class Generation {
boolean broken = false;
}
/** CyclicBarrier鎖—構造一個屏障實例(不帶觸發任務的) */
public CyclicBarrier(int parties) {
this(parties, null);
}
/** CyclicBarrier鎖—構造一個屏障實例(帶觸發任務的) */
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
/** CyclicBarrier鎖—無參數構造一個等待方法(默認模式) */
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
/** CyclicBarrier鎖—有參數構造一個等待方法(支持超時機制) */
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
/** CyclicBarrier鎖—更新狀態變量 */
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
/** CyclicBarrier鎖—阻塞屏障 */
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
/** CyclicBarrier鎖—阻塞屏障 */
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// [1].實例化構建ReentrantLock的對象
final ReentrantLock lock = this.lock;
// [2].通過lock()獲取鎖或者說加鎖操作
lock.lock();
try {
// [3].實例化構建Generation屏障實例對象
final Generation g = generation;
// [4].判斷Generation屏障實例標記狀態是否爲true
if (g.broken)
throw new BrokenBarrierException();
// [5].判斷Thread是包含中斷標誌位
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// [6].對倒計數器的屏障數量遞減1運算
int index = --count;
// [7].依據結果index == 0表示當前指定的線程數量到達屏障最大值,需要觸發Runnable任務
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 進行下一輪屏障設置
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// [7].自旋操作
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 {
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
// 如果等待時間超過指定超時時間,throw new TimeoutException
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 最後釋放鎖操作
lock.unlock();
}
}
/** CyclicBarrier鎖—獲取當前等屏障等待數量 */
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
/** CyclicBarrier鎖—獲取當前等屏障數量 */
public int getParties() {
return parties;
}
/** CyclicBarrier鎖—判斷當前屏障 */
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
/** CyclicBarrier鎖—重置屏障數量 */
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
}
主要需要注意如下幾個方法,都是基於ReentrantLock來實現加鎖和解鎖操作的,其中:
- getNumberWaiting()方法: 獲取當前屏障中等待的線程數量
- reset() 方法:當一輪屏障操作結束,需要重置屏障中最大線程數量
- isBroken() 方法:判斷是否到達屏障最大值
綜上所述,從一定意義上講,CyclicBarrier是一種可重入鎖,屬於ReentrantLock的應用實例,其中加鎖和解鎖操作都是獨佔模式的。
八.Semaphore(信號量)的設計與實現
在Java領域中,Semaphore(信號量)是針對於Java多線程併發控制中實現對公共資源的線程數量進行併發同時訪問控制,主要是採用指定一個最大許可數的思想和基於AQS基礎同步器來實現的一種同步器工具類。
Semaphore可以用來控制在同一時刻訪問共享資源的線程數量,通過協調各個線程以保證共享資源的合理使用。
Semaphore維護了一組虛擬許可,它的數量可以通過構造器的參數指定。
線程在訪問共享資源前,必須調用Semaphore的acquire()方法獲得許可,如果許可數量爲0,該線程就一直阻塞。
線程在訪問共享資源後,必須調用Semaphore的release()方法釋放許可。
1. 設計思想
一般來說,通過定義一個倒計數器,爲了控制最多N個線程同時訪問公共資源,其計數器的最大值Max(N)是被許可的最多N個線程數量,即就是許可的最大值N。
Semaphore類最早是在JDK1.5版本提供的,從設計思想上來看,主要包括倒計數器的最大許可數,同步器工作模式,獲取鎖方法,釋放鎖方法等4個核心要素。其中:
- 同步器工作模式:基於AQS基礎抽象隊列同步器封裝內置實現一個靜態的內置同步器抽象類,然後基於這個抽象類分別實現了公平同步器和非公平同步器,用來指定和描述同步器工作模式是公平模式還是非公平模式。
- 公平/非公平模式:主要描述的是多個線程在同時獲取鎖時是否按照先到先得的順序獲取鎖,如果是則爲公平模式,否則爲非公平模式。
- 獲取鎖方法:主要定義了一個lock()方法來獲取鎖,表示假如鎖已經被其他線程佔有或持有,其當前獲取鎖的線程則進入等待狀態。
- 釋放鎖方法:主要定義了一個unlock()方法來釋放鎖,表示假如鎖已經被其他線程放棄或釋放,其當前獲取鎖的線程則獲得該鎖。
2. 基本實現
在JDK1.8版本中,對於Semaphore的基本實現如下:
public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210 L;
/**
* Semaphore鎖- 封裝同步器
*/
private final Sync sync;
/**
* Semaphore鎖- 封裝同步器
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
//....其他代碼
}
/**
* Semaphore鎖- 構造一個令牌許可(默認非公模式)
*/
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
/**
* Semaphore鎖- 構造一個令牌許可(可選公平/非公模式)
*/
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
/**
* Semaphore鎖- 獲取鎖方法(默認一個且可中斷機制)
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Semaphore鎖- 獲取鎖方法(可選指定多個且可中斷機制)
*/
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
/**
* Semaphore鎖- 獲取鎖方法(默認多個且不可中斷機制)
*/
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
/**
* Semaphore鎖- 獲取鎖方法(指定多個且不可中斷機制)
*/
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
/**
* Semaphore鎖-釋放鎖方法(默認一個)
*/
public void release() {
sync.releaseShared(1);
}
/**
* Semaphore鎖-釋放鎖方法(可選指定多個)
*/
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
}
- 內部同步器:基於AQS基礎同步器封裝和定義了一個靜態內部Sync抽象類,其中抽象了一個內置鎖lock()方法
- 同步器工作模式:主要提供了 2個構造方法,其中無參數構造方法表示的是默認的工作模式,有參數構造方法主要依據參數來實現指定的工作模式
- 獲取鎖方法: 主要提供了3個基於acquire方法,用於獲取鎖共享鎖,其中:
- 無參數acquire()方法:獲取共享鎖的一般模式,默認指定一個許可和支持可中斷機制
- 有參數acquire()方法:獲取共享鎖的指定模式,可選指定多個許可且支持可中斷機制
- 無參數acquireUninterruptibly()方法:獲取共享鎖的指定模式,默認指定一個許可且不支持可中斷機制
- 釋放鎖方法: 主要是提供了2個release()方法用於釋放鎖共享鎖,其中:
- 無參數release()方法:釋放共享鎖的一般模式,默認指定一個許可和支持可中斷機制
- 有參數release()方法:釋放共享鎖的指定模式,可選指定多個許可且支持可中斷機制
2.1 基於AQS同步器封裝靜態內部Sync抽象類
/**
* Semaphore鎖- 基於AQS基礎同步器封裝同步器
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933 L;
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
/**
* Semaphore鎖- 非公平模式獲取共享鎖
*/
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
/**
* Semaphore鎖- 釋放共享鎖
*/
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
/**
* Semaphore鎖- 自旋+compareAndSetState通過CAS操作計算操作令牌許可數
*/
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
/**
* Semaphore鎖- 自旋+compareAndSetState通過CAS操作重置令牌許可數
*/
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
- 實現方式:主要是基於AQS基礎同步器封裝了一個靜態的的Sync抽象類,通過構造方法指定一個最大的令牌許可數量
- 主要方法:主要是看共享鎖的獲取nonfairTryAcquireShared()方法和釋放鎖tryReleaseShared()方法,其中:
- 獲取鎖nonfairTryAcquireShared()方法:非公平模式下獲取共享鎖,利用自旋+compareAndSetState()方法通過CAS操作,保證併發修改令牌許可數量
- 釋放鎖tryReleaseShared(i)方法: 公平/非公平模式下釋放共享鎖,利用自旋+compareAndSetState()方法通過CAS操作釋放,會把釋放的令牌許可數量增加到當前剩餘的令牌許可數量中。
- 令牌許可操作方法:主要提供了drainPermits() 方法 和reducePermits() 方法,其中:
- drainPermits() 方法:主要是利用自旋+compareAndSetState()方法通過CAS操作重置令牌許可數
- reducePermits() 方法:主要是自旋+compareAndSetState)方法通過CAS操作遞減計算操作令牌許可數
- 獲取鎖方式:令牌許可數量QS基礎同步器狀態變量對應,通過getPermits() 方法來獲取令牌許可數量,本質是調用AQS基礎同步器中的getState()來獲取狀態變量。
特別指出的是,這裏的非公平模式主要描述的是,在令牌許可數量允許的情況下,讓所有線程進行自旋操作,其實就是不關心線程到來的順序,將全部線程放到一起去參與競爭令牌許可。
其中,主要還利用compareAndSetState方法來進行CAS操作,保證修改令牌許可數量的原子性操作。
一般來說,假設我們允許控制的最大線程數量爲N,剩餘令牌許可數量爲Remanent(N), 當前可用令牌許可數量爲Current(N) , 消耗令牌許可數量爲Reduction(N),那麼我們會得到一個計算公式:
�即就意味着,剩餘令牌許可數量等於當前可用令牌許可數量與消耗令牌許可數量之差。
由此可見,在公平/非公平模式下,我們對於對於獲取鎖和釋放鎖時,對於剩餘令牌許可數量Remanent(N)計算都滿足以下公式:
- 首先,在線程在訪問共享資源前,我們可以允許的最大值爲Available(N),自旋獲取鎖的數量爲Acquires(N),那麼我們在獲取鎖時:
- �其次,在線程在訪問共享資源後,自旋釋放鎖的數量爲Releases(N),那麼我們在釋放鎖時:
當然,需要注意的的一個問題,就是當剩餘令牌許可數量Remanent(N) < 0時,表示當前線程會進入阻塞等待狀態。
2.2 基於Sync抽象類封裝FairSync公平同步器
/**
* Semaphore鎖- 基於Sync抽象類封裝FairSync公平同步器
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944 L;
/**
* Semaphore鎖- Semaphore鎖- 通過構造方法指定令牌許可
*/
FairSync(int permits) {
super(permits);
}
/**
* Semaphore鎖- Semaphore鎖- 公平模式釋放共享鎖
*/
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
- 實現方式: 主要是在基於靜態內部Sync抽象類來實現,構造了一個可指定大小的的令牌許可
- 主要方法: 主要是提供了一個tryAcquireShared方法,其中利用hasQueuedPredecessors()來保證公平性
- 工作機制: 通過基於AQS基礎同步器中的等待隊列來實現公平機制
需要注意的是,在未達到最大的令牌許可數量時,所有線程都不會進入等待隊列中。
2.3 基於Sync抽象類封裝NonfairSync非公平同步器
/**
* Semaphore鎖- 基於Sync抽象類封裝NonfairSync非公平同步器
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898 L;
/**
* Semaphore鎖- Semaphore鎖- 通過構造方法指定令牌許可
*/
NonfairSync(int permits) {
super(permits);
}
/**
* Semaphore鎖- Semaphore鎖- 非公平模式釋放共享鎖
*/
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
- 實現方式: 主要是在基於靜態內部Sync抽象類來實現,構造了一個可指定大小的的令牌許可
- 主要方法: 主要是提供了一個tryAcquireShared方法,其中主要是調用了靜態內部Sync抽象類nonfairTryAcquireShared方法。
- 工作機制: 通過自旋操作讓所有線程競爭獲取令牌許可,本質還是採用了AQS基礎同步器中闖入策略到打破公平的
3. 具體實現
在JDK1.8版本中,對於Semaphore的具體實現如下:
public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210 L;
/**
* Semaphore鎖- 封裝同步器
*/
private final Sync sync;
/**
* Semaphore鎖- 基於AQS基礎同步器封裝同步器
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933 L;
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
/**
* Semaphore鎖- 非公平模式獲取共享鎖
*/
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
/**
* Semaphore鎖- 釋放共享鎖
*/
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
/**
* Semaphore鎖- 自旋+compareAndSetState通過CAS操作計算操作令牌許可數
*/
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
/**
* Semaphore鎖- 自旋+compareAndSetState通過CAS操作重置令牌許可數
*/
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
/**
* Semaphore鎖- 基於Sync抽象類封裝FairSync公平同步器
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944 L;
/**
* Semaphore鎖- Semaphore鎖- 通過構造方法指定令牌許可
*/
FairSync(int permits) {
super(permits);
}
/**
* Semaphore鎖- Semaphore鎖- 公平模式釋放共享鎖
*/
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
/**
* Semaphore鎖- 基於Sync抽象類封裝NonfairSync非公平同步器
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898 L;
/**
* Semaphore鎖- Semaphore鎖- 通過構造方法指定令牌許可
*/
NonfairSync(int permits) {
super(permits);
}
/**
* Semaphore鎖- Semaphore鎖- 非公平模式釋放共享鎖
*/
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
/**
* Semaphore鎖- 構造一個令牌許可(默認非公模式)
*/
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
/**
* Semaphore鎖- 構造一個令牌許可(可選公平/非公模式)
*/
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
/**
* Semaphore鎖- 獲取鎖方法(默認一個且可中斷機制)
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Semaphore鎖- 獲取鎖方法(可選指定多個且可中斷機制)
*/
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
/**
* Semaphore鎖- 獲取鎖方法(默認多個且不可中斷機制)
*/
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
/**
* Semaphore鎖- 獲取鎖方法(指定多個且不可中斷機制)
*/
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
/**
* Semaphore鎖-釋放鎖方法(默認一個)
*/
public void release() {
sync.releaseShared(1);
}
/**
* Semaphore鎖-釋放鎖方法(可選指定多個)
*/
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
/**
* Semaphore鎖-嘗試獲取鎖方法(默認一個)
*/
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
/**
* Semaphore鎖-嘗試獲取鎖方法(可選指定多個)
*/
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
/**
* Semaphore鎖-嘗試獲取鎖方法(可選指定多個並且支持超時機制)
*/
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
/**
* Semaphore鎖-嘗試獲取鎖方法(默認一個並且支持超時機制)
*/
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* Semaphore鎖-統計可以令牌許可數
*/
public int availablePermits() {
return sync.getPermits();
}
/**
* Semaphore鎖-重置令牌許可數
*/
public int drainPermits() {
return sync.drainPermits();
}
/**
* Semaphore鎖-遞減計算令牌許可數
*/
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
/**
* Semaphore鎖-判斷是否公平模式
*/
public boolean isFair() {
return sync instanceof FairSync;
}
/**
* Semaphore鎖-判斷隊列中是否存在線程對象
*/
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
/**
* Semaphore鎖-獲取隊列長度
*/
public final int getQueueLength() {
return sync.getQueueLength();
}
/**
* Semaphore鎖-獲取隊列的線程對象
*/
protected Collection < Thread > getQueuedThreads() {
return sync.getQueuedThreads();
}
}
- 信號量同步器: 主要是提供了2個構造方法來實現令牌許可的管理,其中:
- 默認非公平模式:依據指定傳入的令牌許可數量permits直接實例化NonfairSync非公平同步器
- 可選公平/非公平模式:依據指定傳入的令牌許可數量permits和公平標記fair來實例化NonfairSync非公平同步器和FairSync公平同步器,其中,當fair=true時,是公平平模式,否則爲非公平模式
- �支持可中斷機制:主要是提供了2個acquire()方法來獲取鎖,其中:
- 無參數acquire()方法:一般模式獲取共享鎖,主要是基於AQS基礎同步器中的acquireSharedInterruptibly(int arg)來實現,其核心邏輯是doAcquireSharedInterruptibly(int arg)來操縱。
- 有參數acquire()方法:依據指定傳入的令牌許可數量permits來判斷,當permits< 0時,直接throw new IllegalArgumentException();否則,直接調用acquireSharedInterruptibly(permits)方法。
- 支持不可中斷機制:主要是提供了2個acquireUninterruptibly() 方法,其中:
- 無參數acquireUninterruptibly() 方法:一般模式獲取共享鎖,主要是基於AQS基礎同步器中acquireShared(int arg)方法來實現,其核心邏輯是doAcquireShared(int arg) 來操縱。
- 有參數acquireUninterruptibly() 方法:依據指定傳入的令牌許可數量permits來判斷,當permits< 0時,直接throw new IllegalArgumentException();否則,直接調用acquireShared(int arg)方法。
- 非公平模式獲取鎖方式: 主要提供了2個tryAcquire() 方法,其中:
- 無參數tryAcquire() 方法:非公平模式嘗試獲取共享鎖,直接調用的是非公平同步器中的nonfairTryAcquireShared(int acquires) 方法。
- 有參數tryAcquire() 方法:依據指定傳入的令牌許可數量permits來判斷,當permits< 0時,直接throw new IllegalArgumentException();否則,直接調用nonfairTryAcquireShared(int acquires) 方法。
- 公平模式獲取鎖方式:主要提供了2個tryAcquire() 方法,支持超時機制。其中:
- 無參數tryAcquire() 方法:公平模式嘗試獲取共享鎖,依據指定傳入的令牌許可數量permits來判斷,當permits< 0時,直接throw new IllegalArgumentException();否則,直接調用的是AQS基礎同步器中的tryAcquire(int permits, long timeout, TimeUnit unit)方法,其核心邏輯是tryAcquireSharedNanos(int arg, long nanosTimeout)來操縱。
- 有參數tryAcquire() 方法:公平模式嘗試獲取共享鎖,默認支持一個許可,直接調用的是AQS基礎同步器中的tryAcquire(1,long timeout, TimeUnit unit)方法,其核心邏輯是tryAcquireSharedNanos(int arg, long nanosTimeout)來操縱。
- 釋放鎖操作方式:主要提供了2個release()方法,其中:
- 無參數release() 方法:公平/非公平模式示範鎖操作,默認支持一個許可,主要是直接調用AQS基礎同步器中的releaseShared(int arg) 方法
- 有參數release() 方法:公平/非公平模式示範鎖操作,依據指定傳入的令牌許可數量permits來判斷,當permits< 0時,直接throw new IllegalArgumentException();否則,主要是直接調用AQS基礎同步器中的releaseShared(int arg) 方法
- 令牌許可操作方法:主要提供了availablePermits() 方法,reducePermits(int reduction)方法 以及drainPermits() 方法,其中:
- availablePermits() 方法:獲取可用的令牌許可數量,主要是調用內部同步器中getPermits()方法。
- reducePermits()方法:計算剩餘可用令牌許可數量,依據指定傳入的令牌許可數量reduction來判斷,當reduction< 0時,直接throw new IllegalArgumentException();否則,調用內部同步器中reducePermits()方法。
- drainPermits() 方法:重置可用令牌許可數量,主要是調用內部同步器中drainPermits()方法。
- 隊列操作方法:主要提供了hasQueuedThreads()方法,getQueuedThreads() 方法以及getQueueLength() 方法,其中:
- hasQueuedThreads()方法:主要是用於獲取隊列中是否存在等待獲取令牌許可的線程對象,主要是直接使用AQS基礎同步器的hasQueuedThreads()來實現。
- getQueuedThreads() 方法:主要是用於獲取隊列中等待獲取令牌許可的線程對象,主要是直接使用AQS基礎同步器的getQueuedThreads()來實現。
- getQueueLength() 方法:主要是用於獲取隊列中等待獲取令牌許可的數量,主要是直接使用AQS基礎同步器的getQueueLength()來實現。
綜上所述,從一定意義上講,Semaphore是一種共享鎖,屬於AQS基礎抽象隊列同步器中共享模式孵化的產物,支持公平模式與非公平模式,默認是使用非公平模式。
寫在最後
通過對Java領域中,JDK內部提供的各種鎖的實現來看,一直圍繞的核心主要還是基於AQS基礎同步器來實現的,但是AQS基礎同步器不是一種非它不可的技術標準規範,更多的只是一套技術參考指南。
但是,實際上,Java對於鎖的實現與運用遠遠不止這些,還有相位器(Phaser)和交換器(Exchanger),以及在Java JDK1.8版本之前併發容器ConcurrentHashMap中使用的分段鎖(Segment)。
不論是何種實現和應用,在Java併發編程領域來講,都是圍繞線程安全問題的角度去考慮的,只是針對於各種各樣的業務場景做的具體的實現。
一定意義上來講,對線程加鎖只是併發編程的實現方式之一,相對於實際應用來說,Java領域中的鎖都只是一種單一應用的鎖,只是給我們掌握Java併發編程提供一種思想沒,三言兩語也不可能詳盡。
到此爲止,這算是對於Java領域中併發鎖的最終章,文中表述均爲個人看法和個人理解,如有不到之處,忘請諒解也請給予批評指正。
最後,技術研究之路任重而道遠,願我們熬的每一個通宵,都撐得起我們想在這條路上走下去的勇氣,未來仍然可期,與各位程序編程君共勉!
版權聲明:本文爲博主原創文章,遵循相關版權協議,如若轉載或者分享請附上原文出處鏈接和鏈接來源。