JDK8的AQS源碼學習

1.JDK8的AQS源碼學習

1.1.帶着問題學習

1.AQS是什麼鬼東西
2.AQS是怎麼實現的

1.2.基本介紹

在併發編程中,Doug Lea大師爲我們提供了大量實用,高性能的工具類,Doug Lea,concurrent 包的作者,編程不識Doug Lea,寫盡java也枉然。

// 同步隊列,是一個帶頭結點的雙向鏈表,用於實現鎖的語義
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements Serializable {
......
}

所謂AQS,指的是AbstractQueuedSynchronizer,它提供了一種實現阻塞鎖和一系列依賴FIFO等待隊列的同步器的框架ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等併發類均是基於AQS來實現的,具體用法是通過繼承AQS實現其模板方法,然後將子類作爲同步組件的內部類。

註釋:後面學習ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier的時候會看到均是基於AQS來實現的這裏不做拓展。

1.3.基本框架

AQS是一個同步器,設計模式是模板模式。

核心數據結構:雙向鏈表 + state(鎖狀態)

底層操作:CAS

AQS基本框架如下圖所示:

在這裏插入圖片描述

1.3.1AbstractQueuedSynchronizer類

在這裏插入圖片描述

1.3.2.Node內部類

在這裏插入圖片描述

1.3.4.state

首先說一下共享資源變量state,它是int數據類型的,其訪問方式有3種:

  • getState()
  • setState(int newState)
  • compareAndSetState(int expect, int update)

重入鎖計數/許可證數量,在不同的鎖中,使用方式有所不同

上述3種方式均是原子操作,其中compareAndSetState()的實現依賴於Unsafe的compareAndSwapInt()方法。

AQS維護了一個volatile語義(支持多線程下的可見性)的共享資源變量state和一個FIFO線程等待隊列(多線程競爭state被阻塞時會進入此隊列)。

// 重入鎖計數/許可證數量,在不同的鎖中,使用方式有所不同
private volatile int state;

// 具有內存讀可見性語義
protected final int getState() {
return state;
}

// 具有內存寫可見性語義
protected final void setState(int newState) {
state = newState;
}

// 具有內存讀/寫可見性語義
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AQS中的int類型的state值,各種鎖就是通過CAS(樂觀鎖)去修改state的值。lock的基本操作還是通過樂觀鎖來實現的。

1.3.5.CLH隊列(FIFO)

AQS是通過內部類Node來實現FIFO隊列的,源代碼解析如下:

static final class Node {

// 表明節點在共享模式下等待的標記
static final Node SHARED = new Node();
// 表明節點在獨佔模式下等待的標記
static final Node EXCLUSIVE = null;

// 表徵等待線程已取消的
static final int CANCELLED = 1;
// 表徵需要喚醒後續線程
static final int SIGNAL = -1;
// 表徵線程正在等待觸發條件(condition)
static final int CONDITION = -2;
// 表徵下一個acquireShared應無條件傳播
static final int PROPAGATE = -3;

/**
* SIGNAL: 當前節點釋放state或者取消後,將通知後續節點競爭state。
* CANCELLED: 線程因timeout和interrupt而放棄競爭state,當前節點將與state徹底拜拜
* CONDITION: 表徵當前節點處於條件隊列中,它將不能用作同步隊列節點,直到其waitStatus被重置爲0
* PROPAGATE: 表徵下一個acquireShared應無條件傳播
* 0: None of the above
*/
volatile int waitStatus;

// 前繼節點
volatile Node prev;
// 後繼節點
volatile Node next;
// 持有的線程
volatile Thread thread;
// 鏈接下一個等待條件觸發的節點
Node nextWaiter;

// 返回節點是否處於Shared狀態下
final boolean isShared() {
return nextWaiter == SHARED;
}

// 返回前繼節點
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}

// Shared模式下的Node構造函數
Node() {
}

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

// 用於Condition
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
private transient volatile Node head; // 【|同步隊列|】的頭結點
private transient volatile Node tail; // 【|同步隊列|】的尾結點



// 前繼節點
volatile Node prev;
// 後繼節點
volatile Node next;

最後我們可以發現鎖的存儲結構就兩個東西:“雙向鏈表” + “state(鎖狀態)”。

需要注意的是,他們的變量都被"transientvolatile修飾。

還可以看到,waitStatus非負的時候,表徵不可用,正數代表處於等待狀態,所以waitStatus只需要檢查其正負符號即可,不用太多關注特定值。

1.3.6.資源的共享方式分爲2種(後面細講)

獨佔式(Exclusive)

只有單個線程能夠成功獲取資源並執行,如ReentrantLock。

獲取資源

  • public final void acquire(int arg) 申請獨佔鎖,允許阻塞帶有中斷標記的線程(會先將其標記清除)

釋放資源

  • public final boolean release(int arg) 釋放鎖,如果鎖已被完全釋放,則喚醒後續的阻塞線程。返回值表示本次操作後鎖是否自由

共享式(Shared)

多個線程可成功獲取資源並執行,如Semaphore/CountDownLatch等。

獲取資源

  • public final void acquireShared(int arg) 申請共享鎖,若獲取成功則直接返回,若失敗,則進入等待隊列,執行自旋獲取資源。

釋放資源

  • public final boolean releaseShared(int arg) 釋放鎖,並喚醒排隊的結點。

AQS需要子類複寫的方法均沒有聲明爲abstract,目的是避免子類需要強制性覆寫多個方法,因爲一般自定義同步器要麼是獨佔方法,要麼是共享方法,只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。

當然,AQS也支持子類同時實現獨佔和共享兩種模式,如ReentrantReadWriteLock。

AQS在獨佔和共享兩種模式下,在acquire()和acquireShared()方法中,線程在阻塞過程中均是忽略中斷的。

2.總結

AQS指的是AbstractQueuedSynchronizer

J.U.C是基於AQS實現的,AQS是一個同步器,設計模式是模板模式。

核心數據結構:雙向鏈表 + state(鎖狀態)

底層操作:CAS

3.參考

非常感謝康建偉分享的jdk源碼筆記

https://github.com/kangjianwei/LearningJDK

淺談Java的AQS:

https://www.jianshu.com/p/0f876ead2846

一文帶你理解Java中Lock的實現原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章