您好,登錄后才能下訂單哦!
(1)AQS是什么?
(2)AQS的定位?
(3)AQS的實現原理?
(4)基于AQS實現自己的鎖?
AQS的全稱是AbstractQueuedSynchronizer,它的定位是為Java中幾乎所有的鎖和同步器提供一個基礎框架。
AQS是基于FIFO的隊列實現的,并且內部維護了一個狀態變量state,通過原子更新這個狀態變量state即可以實現加鎖解鎖操作。
本章及后續章節的內容理解起來可能會比較晦澀,建議先閱讀彤哥上一章的內容【死磕 java同步系列之自己動手寫一個鎖Lock】。
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;
// 標識線程等待在一個條件上
static final int CONDITION = -2;
// 標識后面的共享鎖需要無條件的傳播(共享鎖需要連續喚醒讀的線程)
static final int PROPAGATE = -3;
// 當前節點保存的線程對應的等待狀態
volatile int waitStatus;
// 前一個節點
volatile Node prev;
// 后一個節點
volatile Node next;
// 當前節點保存的線程
volatile Thread thread;
// 下一個等待在條件上的節點(Condition鎖時使用)
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() { // Used to establish initial head or SHARED marker
}
// 節點的構造方法
Node(Thread thread, Node mode) { // Used by addWaiter
// 把共享模式還是互斥模式存儲到nextWaiter這個字段里面了
this.nextWaiter = mode;
this.thread = thread;
}
// 節點的構造方法
Node(Thread thread, int waitStatus) { // Used by Condition
// 等待的狀態,在Condition中使用
this.waitStatus = waitStatus;
this.thread = thread;
}
}
典型的雙鏈表結構,節點中保存著當前線程、前一個節點、后一個節點以及線程的狀態等信息。
// 隊列的頭節點
private transient volatile Node head;
// 隊列的尾節點
private transient volatile Node tail;
// 控制加鎖解鎖的狀態變量
private volatile int state;
定義了一個狀態變量和一個隊列,狀態變量用來控制加鎖解鎖,隊列用來放置等待的線程。
注意,這幾個變量都要使用volatile關鍵字來修飾,因為是在多線程環境下操作,要保證它們的值修改之后對其它線程立即可見。
這幾個變量的修改是直接使用的Unsafe這個類來操作的:
// 獲取Unsafe類的實例,注意這種方式僅限于jdk自己使用,普通用戶是無法這樣調用的
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 狀態變量state的偏移量
private static final long stateOffset;
// 頭節點的偏移量
private static final long headOffset;
// 尾節點的偏移量
private static final long tailOffset;
// 等待狀態的偏移量(Node的屬性)
private static final long waitStatusOffset;
// 下一個節點的偏移量(Node的屬性)
private static final long nextOffset;
static {
try {
// 獲取state的偏移量
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
// 獲取head的偏移量
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
// 獲取tail的偏移量
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
// 獲取waitStatus的偏移量
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
// 獲取next的偏移量
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
// 調用Unsafe的方法原子更新state
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
關于Unsafe類的講解請參考彤哥之前寫的【死磕 java魔法類之Unsafe解析】。
我們可以看到AQS的全稱是AbstractQueuedSynchronizer,它本質上是一個抽象類,說明它本質上應該是需要子類來實現的,那么子類實現一個同步器需要實現哪些方法呢?
// 互斥模式下使用:嘗試獲取鎖
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 互斥模式下使用:嘗試釋放鎖
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式下使用:嘗試獲取鎖
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式下使用:嘗試釋放鎖
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 如果當前線程獨占著鎖,返回true
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
問題:這幾個方法為什么不直接定義成抽象方法呢?
因為子類只要實現這幾個方法中的一部分就可以實現一個同步器了,所以不需要定義成抽象方法。
下面我們通過一個案例來介紹AQS中的部分方法。
直接上代碼:
public class MyLockBaseOnAqs {
// 定義一個同步器,實現AQS類
private static class Sync extends AbstractQueuedSynchronizer {
// 實現tryAcquire(acquires)方法
@Override
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 實現tryRelease(releases)方法
@Override
protected boolean tryRelease(int releases) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
// 聲明同步器
private final Sync sync = new Sync();
// 加鎖
public void lock() {
sync.acquire(1);
}
// 解鎖
public void unlock() {
sync.release(1);
}
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
MyLockBaseOnAqs lock = new MyLockBaseOnAqs();
CountDownLatch countDownLatch = new CountDownLatch(1000);
IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
lock.lock();
try {
IntStream.range(0, 10000).forEach(j -> {
count++;
});
} finally {
lock.unlock();
}
// System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
}, "tt-" + i).start());
countDownLatch.await();
System.out.println(count);
}
}
運行main()方法總是打印出10000000(一千萬),說明這個鎖也是可以直接使用的,當然這也是一個不可重入的鎖。
是不是很簡單,只需要簡單地實現AQS的兩個方法就完成了上一章彤哥自己動手實現的鎖的功能。
它是怎么實現的呢?
我們這一章先不講源碼,后面學習了ReentrantLock自然就明白了。
這一章就到此結束了,本篇沒有去深入的解析AQS的源碼,筆者認為這沒有必要,因為對于從來都沒有看過鎖相關的源碼的同學來說,一上來就講AQS的源碼肯定會一臉懵逼的,具體的源碼我們穿插在后面的鎖和同步器的部分來學習,等所有跟AQS相關的源碼學習完畢了,再來一篇總結。
下面總結一下這一章的主要內容:
(1)AQS是Java中幾乎所有鎖和同步器的一個基礎框架,這里說的是“幾乎”,因為有極個別確實沒有通過AQS來實現;
(2)AQS中維護了一個隊列,這個隊列使用雙鏈表實現,用于保存等待鎖排隊的線程;
(3)AQS中維護了一個狀態變量,控制這個狀態變量就可以實現加鎖解鎖操作了;
(4)基于AQS自己動手寫一個鎖非常簡單,只需要實現AQS的幾個方法即可。
上一章彤哥自己動手寫的鎖,其實可以看成是AQS的一個縮影,看懂了那個基本上AQS可以看懂一半了,因為彤哥那個里面沒有寫Condition相關的內容,下一章ReentrantLock重入鎖中我們將一起學習Condition相關的內容。
所以呢,還是建議大家去看看這篇文章,點擊下面的推薦閱讀可以直達。
死磕 java同步系列之自己動手寫一個鎖Lock
死磕 java魔法類之Unsafe解析
死磕 java同步系列之JMM(Java Memory Model)
死磕 java同步系列之volatile解析
歡迎關注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。