AQS框架學習

0 AQS簡介

0.1 核心思想

抽象隊列同步器實現了鎖機制,可以實現共享鎖和互斥鎖;在某一工作線程想要持有該鎖時,若該鎖能夠被其持有(資源空閒或共享鎖),那麼AQS會將該線程維護爲有效的工作線程;若該鎖不能被其持有(資源非空閒),AQS會利用其實現的一套線程等待及被喚醒時的鎖分配機制去維護這個線程

在AQS的實現上,主要用了以下幾個點:

  • CLH隊列,維護獲取共享資源失敗的阻塞線程
  • 自旋鎖,與cas配合,更新AQS中維護的屬性
  • cas,與自旋鎖配合,更新AQS中維護的屬性
  • LockSupport的靜態方法park()和unpark(),實現線程的阻塞和喚醒

0.2 AQS幾個關鍵屬性

// 維護線程的節點,隊列中維護的就是Node
static final class Node {
	...
	// 用於waitStatus,這裏只列舉了學習到的
	// 指示當前Node的後繼需要unpart()操作
	/** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL = -1;

	// 節點狀態,初始爲0
	volatile int waitStatus;

	// 指向隊列前一個節點
	volatile Node prev;

	// 指向隊列後一個節點
	volatile Node next;
	
	// 維護的線程
	volatile Thread thread;
	...
}

// 指向隊列的隊首,lazily initialized
private transient volatile Node head;

// 指向隊列的隊尾,lazily initialized
private transient volatile Node tail;

// 隊列狀態
private volatile int state; 

0.3 模板設計模式

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();
}
// return true if synchronization is held exclusively
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

這裏的模板方法都聲明爲protected,而非抽象方法,意在讓繼承者選擇需要的方法重寫即可,而不需要將每個方法都實現,降低了代碼的冗餘

1 CLH隊列

這裏參照了https://blog.csdn.net/firebolt100/article/details/82662102進行學習並記錄

1.1 SMP(symmetric multi processing)對稱多處理器介紹

其指一種包括軟硬件的多核計算架構,有兩個及兩個以上的相同的核心共享一塊主存,每個核心在操作系統中的地位相同,可以訪問所有的I/O設備
SMP架構

1.2 CLH隊列實現

1.2.1 CLH隊列鎖思想:

CLH隊列鎖採用主動等待的方式,自旋檢測隊列前一個元素狀態,若檢測到狀態滿足當前元素被喚醒,則結束自旋;自旋實際上是cpu空轉,看起來有些浪費性能,但實際上對於小任務而言,空轉時間非常短暫,相比於阻塞等待的方式,自旋方式的消耗要比阻塞方式時的線程管理和切換的開銷要小

1.2.2 java實現

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class CLHLock {

	/**
	* 每個節點都代表一個申請加鎖的線程對象,active屬性表示該線程處於活躍狀態(true):
	* 1、正在排隊等待加鎖
	* 2、已經獲取到了鎖
	* 非活躍狀態(false)表示當前節點線程工作完畢,後續節點可以結束自旋並開始工作
	*/
	private static class CLHNode {
		volatile boolean active = true; // 默認活躍
	}

	// 隱式鏈表尾節點
	private volatile CLHNode tail = null;

	// 綁定當前線程狀態
	private ThreadLocal<CLHNode> currentThreadNode = new ThreadLocal<>();

	private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> updater = 
		AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, CLHNode.class, "tail");

	// 加鎖
	public void lock() {
		
		// 獲取當前線程綁定狀態
		CLHNode cNode = currentThreadNode.get();
	
		// 未初始化,則綁定一個默認爲活動的線程狀態
		if(cNode == null)
			currentThreadNode.set(cNode = new CLHNode());

		// 維護鏈表,返回前驅節點
		// api返回更新前的值
		CLHNode predecessor = updater.getAndSet(this, cNode);

		// 若有前驅,則需要根據前驅節點的active狀態自旋
		if(predecessor != null)
			for(; predecessor.active;) {}

		// 若無前驅節點,表示可以直接獲取鎖

	}

	// 釋放鎖
	public void unlock() {

		CLHNode cNode = currentThreadNode.get();
		
		// 當前線程不持有鎖或非活躍
		if(cNode == null || !cNode.active)
			return;

		// 移除當前線程的綁定狀態
		currentThreadNode.remove();

		/**
		 * 隊列中所有活躍的非尾節點,都在lock()方法中由predecessor.active引用,因此
		 * 通過cas對活躍的節點設置null是不會成功的,這是便需要對節點的活躍狀態設置爲
		 * false,從而使後繼節點結束自旋;若是尾巴節點,那麼cas操作會成功,而由於是尾
		 * 節點,因此什麼都不需要做,返回即可
		 */
		 if(!updater.compareAndSet(this, cNode, null))
		 	cNode.active = false;

	}

	// 生成任務
	private static Runnable generateTask(final CLHLock, final String taskId) {

		return () -> {
			lock.lock();
			try {
				Thread.sleep(3000); // 模擬線程工作3s
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
			System.err.println(String.format("Thread %s completed.", taskId));
			lock.unlock();
		};

	}

	public static void main(String[] args) {
        final CLHLock lock = new CLHLock();
        for (int i = 0; i < 10; i++)
            new Thread(generateTask(lock, i + "")).start();
    }

}

2 ReentrantLock及AQS應用

通過ReentrantLock,瞭解AQS獨佔鎖模式工作方式

2.1 ReentrantLock demo

利用java.util.concurrent.locks.ReentrantLock實現同步:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {

    static ReentrantLock lock = new ReentrantLock(true); // 公平鎖

    public static void main(String[] args) {
        // 啓動兩個線程
        new Thread(ReentrantLockDemo::testSync, "t1").start();
        new Thread(ReentrantLockDemo::testSync, "t2").start();
    }

    public static void testSync() {
        // 加鎖
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " running ");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " over ");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 在finally塊中,保證發生異常時也能釋放鎖
            lock.unlock();
        }
    }

}

2.2 lock()方法源碼分析

ReentrantLock中維護了繼承自AQS的抽象靜態內部類Sync,有分別有繼承自ReentrantLock.Sync的靜態內部類FairSyncNonfairSync來實現公平鎖和非公平鎖;上述demo使用的是公平鎖,但這裏會將公平鎖和非公平鎖一併列出比對,在實現上只有部分處理不同,大部分是相同的

當執行了lock.lock()後,會調用ReentrantLock.lock(),然後又調用ReentrantLock中Sync.lock()方法:

public void lock() {
	sync.lock();
}

Sync.lock()是抽象方法,分別由FairSyncNonfairSync重寫,公平非公平,就體現在下面FairSyncNonfairSynclock()方法的實現方式中;

// Sync中lock()的聲明
abstract void lock();

// FairSync中的lock
final void lock() {	
	// 根據AQS中鎖狀態及隊列狀態等,嘗試獲取鎖
	acquire(1);
}

// NonfairSync
final void lock() {
	// 不會根據AQS中的隊列狀態,直接嘗試插隊獲取鎖
	if (compareAndSetState(0, 1))
		// 獲取成功後修改AQS中獨佔鎖線程屬性
    	setExclusiveOwnerThread(Thread.currentThread());
    else
    	// 根據AQS中鎖狀態及隊列狀態等,嘗試獲取鎖
        acquire(1);
}

下面是AbstractQueuedSynchronizer.acquire(int arg)的實現,此方法無任何返回,雖然對對interrupt標誌位有過處理,但是調用acquire()方法卻得不到任何有關interrupt的信息,因此也無法對打斷做處理:

public final void acquire(int arg) {
	// 獲取資源失敗,說明存在競爭
	if (!tryAcquire(arg) &&
    	acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    	// acquireQueued()反回線程interrupt標誌位是否被程序復位過,若復位過,
    	// 則進入selfInterrupt()將標誌位再修改爲用戶所操作的標誌的結果
    	selfInterrupt();
}

接下來不再列舉ReentrantLock.NonfairSync的代碼,ReentrantLock.FairSync.tryAcquire()實現,這裏的實現體現了ReentrantLock可重入性

protected final boolean tryAcquire(int acquires) {
	// 獲取當前線程
	final Thread current = Thread.currentThread();
	// 獲取同步器狀態
    int c = getState();
    // 資源空閒
    if (c == 0) {
    	// 阻塞隊列中是否有前驅節點,這裏意爲,即使資源空閒,
    	// 也只有同步隊列爲空或當前節點爲head的下一個節點,才
    	// 能獲取資源
        if (!hasQueuedPredecessors() && 
        	// 沒有前驅節點則cas修改同步器狀態
            compareAndSetState(0, acquires)) {
            // 修改成功,設置同步器持有線程
            setExclusiveOwnerThread(current);
            // 獲取資源成功
            return true;
        }
    }
    // 資源非空閒,當前線程是同步器的持有者
    // 在這裏體現了ReentrantLock的可重入性
    else if (current == getExclusiveOwnerThread()) {
    	// 新狀態 = 當前狀態 + 本次設置的狀態
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 設置狀態
        setState(nextc);
        // 獲取資源成功
        return true;
    }
    // 獲取資源失敗
    return false;
}

AbstractQueuedSynchronizer.hasQueuedPredecessors()代碼實現,這裏主要用於判斷當前線程是否是非head的第一個節點,若是,則有資格獲取共享資源:

/**
 * 若有除head外的線程節點先於當前線程入隊,返回true
 * 若當前線程節點在除去head外的隊列首或隊列爲空,返回false
 */
public final boolean hasQueuedPredecessors() {
 	// The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    // 隊列未初始化,h == t,h和t都爲null
    // 隊列已初始化,兩種情況:
    // 1:隊列元素個數大於1,這時候一定存在線程競爭共享資源,h != t一定成立,並且因爲隊列元素個數大於1,
    // 所以(s = h.next) == null一定爲false;而s作爲head的下一個節點,若當前線程即爲s中的線程,那麼說明其
    // 有資格獲取鎖;從代碼來看,s.thread != Thread.currentThread()結果爲false,因此整個方法返回false
    //
    // 2:隊列元素個數等於1,
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

AbstractQueuedSynchronizer.addWaiter()代碼實現,該方法作用是將當前節點放入阻塞隊列:

// 在ReentrantLock中調用,傳入的mode爲Node.EXCLUSIVE
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;
        // cas設置阻塞隊列尾節點
        // 這裏嘗試快速入隊,如果失敗說明其它線程先入隊了,此時就需要在enq()方法中通過自旋嘗試入隊
        if (compareAndSetTail(pred, node)) {
        	// 設置原尾節點的後繼節點
            pred.next = node;
            // 返回新入隊的節點
            return node;
        }
    }
    // 入隊,來到這個方法,說明當先是存在多線程競爭共享資源的,並且鎖被其它線程持有,還沒有釋放
    enq(node);
    // 返回新入隊節點
    return node;
}

AbstractQueuedSynchronizer.enq()代碼實現,這裏若tail爲null,那麼必須初始化head,並將tail指向head:

private Node enq(final Node node) {
	// 自旋
	for (;;) {
		// 阻塞隊列尾節點
        Node t = tail;
        // 尾節點尾空,必須初始化頭節點,並且將tail也指向head
        if (t == null) { // Must initialize
        	// cas初始化head,注意這裏是將一個空節點設置爲head,也就是doc中描述的dummy header
        	// 存在競爭時,就必須初始化一個空Node並設置爲head
            if (compareAndSetHead(new Node()))
            	// tail指向head
                tail = head;
        } else {
        	// 新節點入隊
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                // 返回原tail
                return t;
            }
        }
    }
}

AbstractQueuedSynchronizer.acquireQueued()代碼實現,該方法作用是,在新節點進入阻塞隊列後,處理新節點所存儲的線程,若線程能獲得資源,則進行工作,否則park()阻塞:

// final Node node, 指新入隊的節點
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
    	// 是否被interrupt
        boolean interrupted = false;
        // 自旋
        // 當線程競爭資源,沒有得到資源的線程將會阻塞在該自旋中,
        // 被喚醒後,仍然在該自旋中嘗試獲取資源;獲取資源成功後,
        // 將當前線程的Node節點設置爲head,並清空Node內的屬性
        for (;;) {
        	// 當前節點的前驅節點
            final Node p = node.predecessor();
            // 前驅節點爲head,表示當前節點有資格競爭資源
            if (p == head && tryAcquire(arg)) {
            	// 競爭資源成功,設置當前節點爲head
                setHead(node);
                // 原head斷鏈
                p.next = null; // help GC
                failed = false;
                // 返回
                return interrupted;
            }
            // 判斷當前線程獲取資源失敗後,是否應該進入阻塞狀態,即前驅節點的waitStatus是否爲Node.Signal
            if (shouldParkAfterFailedAcquire(p, node) &&
            	// 在該方法中調用park()被阻塞,並返回是否被interrupt
            	// 若返回true,也能夠說明線程interrupt標誌位是被複位
            	// 過的,這個復位操作是非用戶操作
                parkAndCheckInterrupt())
                // 代碼進行到這裏意味着程序修改了用戶的行爲,
                // 即用戶將interrupt標誌位修改爲true,而程序
                // 將其復位了
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire()代碼實現:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	int ws = pred.waitStatus;
	if (ws == Node.SIGNAL) // 前驅節點 waitStatus == Node.SIGNAL 則當前節點應該park()
	    return true;
	if (ws > 0) {
	    do {
	        node.prev = pred = pred.prev;
	    } while (pred.waitStatus > 0);
	    pred.next = node;
	} else {
		// 設置前驅節點waitStatus
	    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	}
	return false;
}

AbstractQueuedSynchronizer.parkAndCheckInterrupt()代碼實現:

private final boolean parkAndCheckInterrupt() {
	// 阻塞在當前行,需要其它線程unpark()
    LockSupport.park(this);
    // 判斷當前線程是否被interrupt,並復位flag,這個復位操作修改了用戶的行爲
    return Thread.interrupted();
}

2.3 unlock()方法源碼分析

ReentrantLock.unlock()方法直接調用AbstractQueuedSynchronizer.release()方法進行鎖的釋放,代碼如下:

public void unlock() {
    sync.release(1);
}

AbstractQueuedSynchronizer.release()代碼實現:

public final boolean release(int arg) {
	// 釋放鎖
    if (tryRelease(arg)) {
    	// 獲取頭節點
        Node h = head;
        // head不爲空並且頭節點ws不爲0
        // head爲空說明此前無線程競爭,無線程競爭就不需要初始化head
        if (h != null && h.waitStatus != 0)
        	// 喚醒後繼節點線程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

ReentrantLock.tryRelease()代碼實現如下:

protected final boolean tryRelease(int releases) {
	// ReentrantLock是可重入的,這裏用當前state值減去此次釋放的state值
    int c = getState() - releases;
    // 當前線程不是同步器持有者,拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 標識同步器是否空閒
    boolean free = false;
    // state爲0標識共享資源空閒
    if (c == 0) {
        free = true;
        // 清空同步器持有者線程
        setExclusiveOwnerThread(null);
    }
    // 重置state
    setState(c);
    return free;
}

AbstractQueuedSynchronizer.unparkSuccessor()代碼實現:

// 這裏的入參是同步隊列的dummy header
private void unparkSuccessor(Node node) {
   	// head的waitStatus值
    int ws = node.waitStatus;
    // ws小於0則歸0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * 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.
     */
    // 正常來說,阻塞隊列head的下一個node節點持有即將被喚醒的線程,
    // 但若下一個node節點爲null或其狀態爲Node.CANCELLED,那麼就從tail
    // 開始向前遍歷,直到找到一個可以被喚醒的線程的Node節點
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 從tail開始尋找合適的Node
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
    	// 喚醒線程
        LockSupport.unpark(s.thread);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章