java併發程式設計(三)--java中的鎖(Lock介面和佇列同步器AQS)
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //內部類--節點 static final class Node {…} //等待佇列的頭節點 private transient volatile Node head; //等待佇列的尾節點 private transient volatile Node tail; //同步狀態 private volatile int state; protected final int getState() { return state;} protected final void setState(int newState) { state = newState;} //同步器幾個可重寫的方法 //獨佔式獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS設定同步狀態 protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();} //獨佔式釋放同步狀態,等待獲取同步狀態的執行緒有機會獲取同步狀態 protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();} //共享式獲取同步狀態,返回大於0的值,表示獲取成功,反之,獲取失敗 protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();} //共享式釋放同步狀態 protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();} //當前同步器是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒所佔用 protected boolean isHeldExclusively() {throw new UnsupportedOperationException();} //同步器提供的模板方法 //獨佔式獲取同步狀態,如果當前執行緒獲取同步狀態成功,則由該方法返回,否則,進入同步佇列等待,該方法會呼叫重寫的tryAcquire(int arg)方法 void acquire(int arg) //與acquire(int arg)相同,但該方法響應中斷 void acquireInterruptibly(int arg) //在acquireInterruptibly(int arg)基礎上增加超時限制 boolean tryAcquireNanos(int arg, long nanosTimeout) //共享式獲取同步狀態,與獨佔式的區別在於同一時刻可以有多個執行緒獲取同步狀態 void acquireShared(int arg) //響應中斷 void acquireSharedInterruptibly(int arg) //增加超時限制 boolean tryAcquireSharedNanos(int arg, long nanosTimeout) //獨佔式釋放同步狀態,釋放後,將同步佇列中第一個節點包含的執行緒喚醒 boolean release(int arg) //共享式釋放 boolean releaseShared(int arg) //獲取等待在同步佇列上的執行緒集合 Collection<Thread> getQueuedThread() … }
同步器提供的模板方法基本上分為3類:獨佔式獲取與釋放同步狀態、共享式獲取與釋放同步狀態和查詢同步佇列中的等待執行緒情況。自定義同步元件將使用同步器提供的模板方法來實現自己的同步語義。
4.5 《java併發程式設計的藝術》中自定義同步器的簡單例項
package com.secondbook.thread.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* Created by w1992wishes on 2017/6/1.
*/
public class Mutex implements Lock {
// 靜態內部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer{
// 是否處於佔用狀態
protected boolean isHeldExclusively(){
return getState() == 1;
}
// 當狀態為0的時候獲取鎖
public boolean tryAcquire(int acquires){
if (compareAndSetState(0, 1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//釋放鎖,將當前狀態設定為0
protected boolean tryRelease(int releases){
if (getState() == 0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一個Condition,每個condition都包含了一個condition佇列
Condition newCondition(){
return new ConditionObject();
}
}
// 僅需要將操作代理到Sync上即可
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
sync.release(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked(){
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
}
獨佔鎖Mutex是一個自定義同步元件,它在同一時刻只允許一個執行緒佔有鎖。Mutex中定義了一個靜態內部類,該內部類繼承了同步器並實現了獨佔式獲取和釋放同步狀態。在tryAcquire(int acquires)方法中,如果經過CAS設定成功(同步狀態設定為1),則代表獲取了同步狀態,而在tryRelease(int releases)方法中只是將同步狀態重置為0。使用者使用Mutex時並不會直接和內部同步器的實現打交道,而是呼叫Mutex提供的方法,在Mutex的實現中,以獲取鎖的lock()方法為例,只需要在方法實現中呼叫同步器的模板方法acquire(int args)即可,當前執行緒呼叫該方法獲取同步狀態失敗後會被加入到同步佇列中等待,這樣就大大降低了實現一個可靠自定義同步元件的門檻。
五.佇列同步器的實現分析
同步器依賴內部的同步佇列(一個FIFO雙向佇列)來完成同步狀態的管理,當前執行緒獲取同步狀態失敗時,同步器會將當前執行緒以及等待狀態等資訊構造成為一個節點(Node)並將其加入同步佇列,同時會阻塞當前執行緒,當同步狀態釋放時,會把首節點中的執行緒喚醒,使其再次嘗試獲取同步狀態。
5.1 Node
同步佇列中的節點(Node)是佇列同步器的一個內部類,用來儲存獲取同步狀態失敗的執行緒引用、等待狀態以及前驅和後繼節點。
static final class Node {
//該等待同步的節點處於共享模式
static final Node SHARED = new Node();
//該等待同步的節點處於獨佔模式
static final Node EXCLUSIVE = null;
//等待狀態,這個和state是不一樣的:有1,0,-1,-2,-3五個值
volatile int waitStatus;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile Node prev;//前驅節點
volatile Node next;//後繼節點
volatile Thread thread;//等待鎖的執行緒
//和節點是否共享有關
Node nextWaiter;
//Returns true if node is waiting in shared mode
final boolean isShared() {
return nextWaiter == SHARED;
}
}
waitStatus五個值的含義:
CANCELLED(1):該節點的執行緒可能由於超時或被中斷而處於被取消(作廢)狀態,一旦處於這個狀態,節點狀態將一直處於CANCELLED(作廢),因此應該從佇列中移除。
SIGNAL(-1):當前節點為SIGNAL時,後繼節點會被掛起,因此在當前節點釋放鎖或被取消之後必須被喚醒(unparking)其後繼結點。
CONDITION(-2):該節點的執行緒處於等待條件狀態,在等待佇列中,不會被當作是同步佇列上的節點,直到被喚醒(signal),設定其值為0,從等待佇列轉移到同步佇列中,加入到對同步狀態的獲取中。
PROPAGATE(-3):表示下一次共享式同步狀態獲取將會無條件傳播下去。
0:新加入的節點。
5.2 AQS同步器的結構
節點是構成同步佇列的基礎,同步器擁有首節點(head)和尾節點(tail)。同步佇列的基本結構如下:
同步佇列設定尾節點(未獲取到鎖的執行緒加入同步佇列): 同步器AQS中包含兩個節點型別的引用:一個指向頭結點的引用(head),一個指向尾節點的引用(tail),當一個執行緒成功的獲取到鎖(同步狀態),其他執行緒無法獲取到鎖,而是被構造成節點(包含當前執行緒,等待狀態)加入到同步佇列中等待獲取到鎖的執行緒釋放鎖。這個加入佇列的過程,必須要保證執行緒安全。否則如果多個執行緒的環境下,可能造成新增到佇列等待的節點順序錯誤,或者數量不對。因此同步器提供了CAS原子的設定尾節點的方法(保證一個未獲取到同步狀態的執行緒加入到同步佇列後,下一個未獲取的執行緒才能夠加入)。 如下圖,設定尾節點:
同步佇列設定首節點(原頭節點釋放鎖,喚醒後繼節點):同步佇列遵循FIFO,頭節點是獲取鎖(同步狀態)成功的節點,頭節點在釋放同步狀態的時候,會喚醒後繼節點,而後繼節點將會在獲取鎖(同步狀態)成功時候將自己設定為頭節點。設定頭節點是由獲取鎖(同步狀態)成功的執行緒來完成的,由於只有一個執行緒能夠獲取同步狀態,則設定頭節點的方法不需要CAS保證,只需要將頭節點設定成為原首節點的後繼節點,並斷開原頭結點的next引用。如下圖,設定首節點:
5.3 獨佔式同步狀態獲取
通過呼叫同步器的acquire(intarg)方法可以獲取同步狀態,該方法對中斷不敏感,也就是
由於執行緒獲取同步狀態失敗後進入同步佇列中,後續對執行緒進行中斷操作時,執行緒不會從同
步佇列中移出。
5.3.1同步器的acquire方法原始碼
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上述程式碼主要完成了同步狀態獲取、節點構造、加入同步佇列以及在同步佇列中自旋等待的相關工作,其主要邏輯是:
(1)當前執行緒實現通過tryAcquire()方法嘗試獲取鎖,獲取成功的話直接返回,如果嘗試失敗的話,進入等待佇列排隊等待,可以保證執行緒安全(CAS)的獲取同步狀態。
(2)如果嘗試獲取鎖失敗的話,構造同步節點(獨佔式的Node.EXCLUSIVE),通過addWaiter(Node node,int args)方法,將節點加入到同步佇列的佇列尾部。
(3)最後呼叫acquireQueued(finalNode node, int args)方法,使該節點以死迴圈的方式獲取同步狀態,如果獲取不到,則阻塞節點中的執行緒。acquireQueued方法當前執行緒在死迴圈中獲取同步狀態,而只有前驅節點是頭節點的時候才能嘗試獲取鎖(同步狀態)( p == head && tryAcquire(arg))。
5.3.2addWaiter方法的原始碼
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;
}
上述程式碼通過使用compareAndSetTail(Node expect,Node update)方法來確保節點能夠被執行緒安全新增。
5.3.3enq方法的原始碼
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;
}
}
}
}
在enq(final Node node)方法中,同步器通過“死迴圈”來保證節點的正確新增,在“死迴圈”中只有通過CAS將節點設定成為尾節點之後,當前執行緒才能從該方法返回,否則,當前執行緒不斷地嘗試設定。可以看出,enq(final Node node)方法將併發新增節點的請求通過CAS變得“序列化”了。
5.3.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);
}
}
在acquireQueued(final Node node,int arg)方法中,當前執行緒在“死迴圈”中嘗試獲取同步狀態,而只有前驅節點是頭節點才能夠嘗試獲取同步狀態,原因是:
1.頭結點是成功獲取同步狀態的節點,而頭結點的執行緒釋放鎖以後,將喚醒後繼節點,後繼節點執行緒被喚醒後要檢查自己的前驅節點是否為頭結點。
2.維護同步佇列的FIFO原則,節點進入同步佇列以後,就進入了一個自旋的過程,每個節點(後者說每個執行緒)都在自省的觀察。
節點自旋獲取同步狀態的行為如圖:獨佔式同步狀態獲取流程,也就是acquire(int arg)方法呼叫流程,如圖
5.4 獨佔式鎖的釋放
/* 1. unlock():unlock()是解鎖函式,它是通過AQS的release()函式來實現的。
* 在這裡,“1”的含義和“獲取鎖的函式acquire(1)的含義”一樣,它是設定“釋放鎖的狀態”的引數。
* 由於“公平鎖”是可重入的,所以對於同一個執行緒,每釋放鎖一次,鎖的狀態-1。
unlock()在ReentrantLock.java中實現的,原始碼如下:
*/
public void unlock() {
sync.release(1);
}
5.4.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;
}
5.4.2 tryRelease方法原始碼
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
5.4.3unparkSuccessor方法原始碼
// 4. 喚醒頭結點的後繼節點
private void unparkSuccessor(Node node) {
//獲取頭結點(執行緒)的狀態
int ws = node.waitStatus;
//如果狀態<0,設定當前執行緒對應的鎖的狀態為0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//解釋:Thread to unpark is held in successor, which is normally just the next node.
//But if cancelled or apparently(顯然) null, traverse backwards(向後遍歷) from tail to find the actual(實際的) non-cancelled successor(前繼節點).
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);
}
}
5.5 共享式同步狀態獲取與釋放
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
在acquireShared(int arg)方法中,同步器呼叫tryAcquireShared(int arg)方法嘗試獲取同步狀態,tryAcquireShared(int arg)方法返回值為int型別,當返回值大於等於0時,表示能夠獲取到同步狀態。
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);
}
}
在共享式獲取的自旋過程中,成功獲取到同步狀態並退出自旋的條件就是tryAcquireShared(int arg)方法返回值大於等於0。可以看到,在doAcquireShared(intarg)方法的自旋過程中,如果當前節點的前驅為頭節點時,嘗試獲取同步狀態,如果返回值大於等於0,表示該次獲取同步狀態成功並從自旋過程中退出。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
在釋放同步狀態之後,將會喚醒後續處於等待狀態的節點。對於能夠支援多個執行緒同時訪問的併發元件(比如Semaphore),它和獨佔式主要區別在於tryReleaseShared(int arg)方法必須確保同步狀態(或者資源數)執行緒安全釋放,一般是通過迴圈和CAS來保證的,因為釋放同步狀態的操作會同時來自多個執行緒。
相關推薦
java併發程式設計(三)--java中的鎖(Lock介面和佇列同步器AQS)
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //內部類--節點 static final clas
java併發程式設計一一執行緒池原理分析(三)
合理的設定執行緒池的大小 接著上一篇探討執行緒留下的尾巴。如果合理的設定執行緒池的大小。 要想合理的配置執行緒池的大小、首先得分析任務的特性,可以從以下幾個角度分析: 1、任務的性質:CPU密集型任務、IO密集型任務、混合型任務等; 2、任務的優先順序:高、中、低; 3、任務的執行時
Java併發程式設計(七)佇列同步器AQS
一、AQS簡介 佇列同步器AbstractQueuedSynchronizer(簡稱AQS)是用來構建鎖或其他同步元件的基礎框架,它服務的是鎖的實現者。AQS有一個變量表示同步狀態,通過內建的FIFO管理執行緒排隊,基於AQS可以將同步狀態管理、執行緒排隊、等待與喚醒等操作對鎖遮蔽,簡化鎖的實現
Java併發程式設計:執行緒池的使用(轉載)
轉載自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java併發程式設計:執行緒池的使用 在前面的文章中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會有一個問題: 如果併發的執行緒數量很多,並且每個執行緒都是執行
Java多執行緒程式設計-(12)-Java中的佇列同步器AQS和ReentrantLock鎖原理簡要分析
原文出自 : https://blog.csdn.net/xlgen157387/article/details/78341626 一、Lock介面 在上一篇文章中: Java多執行緒程式設計-(5)-使用Lock物件實現同步以及執行緒間通訊 介紹
JAVA 併發程式設計-執行緒範圍內共享變數(五)
執行緒範圍內共享變數要實現的效果為:多個物件間共享同一執行緒內的變數未實現執行緒共享變數的demo:package cn.itcast.heima2; import java.util.HashMap; import java.util.Map; import java.u
【4】Java併發程式設計:多執行緒中的快取一致性和CAS
一、匯流排鎖定和快取一致性 基本概念 這是兩個作業系統層面的概念。隨著多核時代的到來,併發操作已經成了很正常的現象,作業系統必須要有一些機制和原語,以保證某些基本操作的原子性,比如處理器需要保證讀一個位元組或寫一個位元組是原子的,那麼它是如何實現的呢?有
《Java併發程式設計的藝術》-Java併發包中的讀寫鎖及其實現分析
1. 前言 在Java併發包中常用的鎖(如:ReentrantLock),基本上都是排他鎖,這些鎖在同一時刻只允許一個執行緒進行訪問,而讀寫鎖在同一時刻可以允許多個讀執行緒訪問,但是在寫執行緒訪問時,所有的讀執行緒和其他寫執行緒均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和
C++併發程式設計2——為共享資料加鎖(三)
正交——消除無關事務之間的影響,力求高內聚低耦合。 死鎖的概念略去不說,死鎖有可能發生在使用多個互斥量的場景下,也可能存在沒有使用互斥量的場景: 兩個執行緒都在等待對方釋放互斥量兩個執行緒都呼叫了對方的join()函式 為了解決兩個執行緒都在等待對方釋放互斥量導致的
Java併發程式設計札記-(三)JUC原子類-07CAS
CAS,即compare and swap,比較並交換。CAS操作包含三個運算元:記憶體值(V),預期值(A)、新值(B)。如果記憶體值與預期值相同,就將記憶體值修改為新值,否則不做任何操作。 java.util.concurrent.atomic是建立在CA
Java併發程式設計札記-(三)JUC原子類-05原子方式更新類的指定volatile欄位
AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater和AtomicLongFieldUpdater是基於反射的實用工具,可以提供對關聯欄位型別的訪問。例如AtomicLongFieldUpdater可以對指定
Java併發程式設計隨筆【九】中被丟棄的執行緒組ThreadGroup
執行緒組的初衷是作為一種隔離的機制,當然是出於安全的考慮。但是它們從來沒有真正的履行這個承諾,它們的安全價值已經差到根本不在Java安全模型的標準工作中被提及的地步。 既然執行緒組並沒有提供所提及的任何安全功能,那麼它們到底提供了什麼功能呢?不多,它們允許你同
Java併發程式設計之三:執行緒掛起、恢復與終止的正確方法
出處:http://blog.csdn.NET/ns_code/article/details/17095733 掛起和恢復執行緒 Thread 的API中包含兩個被淘汰的方法,它們用於臨時掛起和重啟某個執行緒,這些方法已經被淘汰,因為它們是不安全的,不穩定的。如果
Java併發程式設計札記-(三)JUC原子類-06JDK1.8新增:LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator
DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder是JDK1.8新增的部分,是對AtomicLong等類的改進。比如LongAccumulator與LongAdder在高併發環境下比AtomicLong
Java 中佇列同步器 AQS(AbstractQueuedSynchronizer)實現原理
#### 前言 在 Java 中通過鎖來控制多個執行緒對共享資源的訪問,使用 Java 程式語言開發的朋友都知道,可以通過 synchronized 關鍵字來實現鎖的功能,它可以隱式的獲取鎖,也就是說我們使用該關鍵字並不需要去關心鎖的獲取和釋放過程,但是在提供方便的同時也意味著其靈活性的下降。例如,有這樣的一
【搞定Java併發程式設計】第10篇:鎖的記憶體語義
上一篇:CAS詳解:https://blog.csdn.net/pcwl1206/article/details/84892287 目 錄: 1、鎖的釋放-獲取建立的happens-before關係 2、釋放鎖和獲取鎖的記憶體語義 3、鎖記憶體語義的實現 4、conc
【搞定Java併發程式設計】第17篇:佇列同步器AQS原始碼分析之共享模式
AQS系列文章: 1、佇列同步器AQS原始碼分析之概要分析 2、佇列同步器AQS原始碼分析之獨佔模式 3、佇列同步器AQS原始碼分析之共享模式 4、佇列同步器AQS原始碼分析之Condition介面、等待佇列 通過上一篇文章的的分析,我們知道獨佔模式獲取同步狀態(或者說獲取鎖
【搞定Java併發程式設計】第16篇:佇列同步器AQS原始碼分析之獨佔模式
AQS系列文章: 1、佇列同步器AQS原始碼分析之概要分析 2、佇列同步器AQS原始碼分析之獨佔模式 3、佇列同步器AQS原始碼分析之共享模式 4、佇列同步器AQS原始碼分析之Condition介面、等待佇列 本文主要講解佇列同步器AQS的獨佔模式:主要分為獨佔式同步狀態獲取
【搞定Java併發程式設計】第15篇:佇列同步器AQS原始碼分析之概要分析
AQS系列文章: 1、佇列同步器AQS原始碼分析之概要分析 2、佇列同步器AQS原始碼分析之獨佔模式 3、佇列同步器AQS原始碼分析之共享模式 4、佇列同步器AQS原始碼分析之Condition介面、等待佇列 先推薦兩篇不錯的博文: 1、一行一行原始碼分析清楚Abstract
【搞定Java併發程式設計】第18篇:佇列同步器AQS原始碼分析之Condition介面、等待佇列
AQS系列文章: 1、佇列同步器AQS原始碼分析之概要分析 2、佇列同步器AQS原始碼分析之獨佔模式 3、佇列同步器AQS原始碼分析之共享模式 4、佇列同步器AQS原始碼分析之Condition介面、等待佇列 通過前面三篇關於AQS文章的學習,我們深入瞭解了AbstractQ