問題
(1)AQS的定位?
(2)AQS的重要組成部分?
(3)AQS運用的設計模式?
(4)AQS的總體流程?
簡介
AQS的全稱是AbstractQueuedSynchronizer,它的定位是為Java中幾乎所有的鎖和同步器提供一個基礎框架。
在之前的章節中,我們一起學習了ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch的原始碼,今天我們一起來對AQS做個總結。
狀態變數state
AQS中定義了一個狀態變數state,它有以下兩種使用方法:
(1)互斥鎖
當AQS只實現為互斥鎖的時候,每次只要原子更新state的值從0變為1成功了就獲取了鎖,可重入是通過不斷把state原子更新加1實現的。
(2)互斥鎖 + 共享鎖
當AQS需要同時實現為互斥鎖+共享鎖的時候,低16位儲存互斥鎖的狀態,高16位儲存共享鎖的狀態,主要用於實現讀寫鎖。
互斥鎖是一種獨佔鎖,每次只允許一個執行緒獨佔,且當一個執行緒獨佔時,其它執行緒將無法再獲取互斥鎖及共享鎖,但是它自己可以獲取共享鎖。
共享鎖同時允許多個執行緒佔有,只要有一個執行緒佔有了共享鎖,所有執行緒(包括自己)都將無法再獲取互斥鎖,但是可以獲取共享鎖。
AQS佇列
AQS中維護了一個佇列,獲取鎖失敗(非tryLock())的執行緒都將進入這個佇列中排隊,等待鎖釋放後喚醒下一個排隊的執行緒(互斥鎖模式下)。
Condition佇列
AQS中還有另一個非常重要的內部類ConditionObject,它實現了Condition介面,主要用於實現條件鎖。
ConditionObject中也維護了一個佇列,這個佇列主要用於等待條件的成立,當條件成立時,其它執行緒將signal這個佇列中的元素,將其移動到AQS的佇列中,等待佔有鎖的執行緒釋放鎖後被喚醒。
Condition典型的運用場景是在BlockingQueue中的實現,當佇列為空時,獲取元素的執行緒阻塞在notEmpty條件上,一旦佇列中新增了一個元素,將通知notEmpty條件,將其佇列中的元素移動到AQS佇列中等待被喚醒。
模板方法
AQS這個抽象類把模板方法設計模式運用地爐火純青,它裡面定義了一系列的模板方法,比如下面這些:
// 獲取互斥鎖
public final void acquire(int arg) {
// tryAcquire(arg)需要子類實現
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 獲取互斥鎖可中斷
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquire(arg)需要子類實現
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
// 獲取共享鎖
public final void acquireShared(int arg) {
// tryAcquireShared(arg)需要子類實現
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 獲取共享鎖可中斷
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared(arg)需要子類實現
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 釋放互斥鎖
public final boolean release(int arg) {
// tryRelease(arg)需要子類實現
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 釋放共享鎖
public final boolean releaseShared(int arg) {
// tryReleaseShared(arg)需要子類實現
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
複製程式碼
獲取鎖、釋放鎖的這些方法基本上都穿插在ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch的原始碼解析中了,現在看他們是不是舒服多了,如果一開始就看這些原始碼,難免會很暈。
需要子類實現的方法
上面一起學習了AQS中幾個重要的模板方法,下面我們再一起學習下幾個需要子類實現的方法:
// 互斥模式下使用:嘗試獲取鎖
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中幾個重要的組成部分,搞明白了這幾個結構,AQS對你將沒有任何祕密可言,當然面試的時候能把這幾個點答清楚,面試官也會眼前一亮的。
(1)狀態變數state;
(2)AQS佇列;
(3)Condition佇列;
(4)模板方法;
(5)需要子類實現的方法;
彩蛋
經過前面的學習,您能簡要描述一下AQS獲取互斥鎖的大體流程嗎?
這裡彤哥就不作答了,相信學習完前面的內容,答這道題不是個問題了,答不上來的還需要把下面的推薦閱讀好好多看幾遍^^
推薦閱讀
3、 死磕 java同步系列之JMM(Java Memory Model)
8、 死磕 java同步系列之ReentrantLock原始碼解析(一)——公平鎖、非公平鎖
9、 死磕 java同步系列之ReentrantLock原始碼解析(二)——條件鎖
10、 死磕 java同步系列之ReentrantLock VS synchronized
11、 死磕 java同步系列之ReentrantReadWriteLock原始碼解析
12、 死磕 java同步系列之Semaphore原始碼解析
13、 死磕 java同步系列之CountDownLatch原始碼解析
歡迎關注我的公眾號“彤哥讀原始碼”,檢視更多原始碼系列文章, 與彤哥一起暢遊原始碼的海洋。