首頁>技術>

一、前言

內部類繼承了AbstractQueuedSynchronize(簡稱AQS),可見,AQS是實現這些類的基石,只要把AQS的原始碼理解透徹了,那些類也就一目瞭然了。廢話少說,我們直接進入主題。

二、AQS的成員變數

前面說了,AQS是ReentrantLock、CountDownLatch等類實現的基石,那麼AQS內部實現了什麼以至於這些類都要依賴於它?

通俗地說就是AQS為這些類提供了一個密碼鎖,這些類擁有了密碼鎖之後就可以自己來設定密碼。原始碼如下:

/** * 同步佇列的頭節點,延遲初始化。 * 除初始化外,只能透過setHead方法進行修改。 * 注意:如果head存在,則保證其waitStatus不為 CANCELLED。 */private transient volatile Node head;/** * 同步佇列的尾節點,延遲初始化。 * 僅透過方法enq新增新的尾節點 */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 final boolean compareAndSetState(int expect, int update) {	// See below for intrinsics setup to support this	return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}

上面的原始碼中列出了AQS的所有成員變數,只有三個:同步佇列的頭節點引用、同步佇列的尾節點引用以及同步狀態。

這三個成員變數都使用了volatile關鍵字進行修飾,目的就是確保多個執行緒對它的修改是記憶體可見的。

整個類的核心就是同步狀態state,它是一個int型的變數,可以充當密碼鎖,state的具體值可以看做密碼控制密碼的開或關。但是這個鎖的密碼是多少是由各個子類來決定的,

例如ReentrantLock,state等於0表示鎖是開的,state大於0表示鎖是關的,而像Semaphore,state大於0表示鎖是開的,state等於0表示鎖是關的。

三、AQS的同步佇列和條件佇列。

AQS內部其實有兩個排隊區,一個是同步佇列,一個是條件佇列。如上圖所示,同步佇列只有一個,而條件佇列可以有多個。

同步佇列的節點分別持有前驅和後繼節點的引用,而條件佇列的節點只有一個指向後繼節點的引用。圖中的T代表執行緒,每個節點包含一個執行緒,執行緒在獲取鎖失敗後首先會進入同步佇列排隊,要想進入條件佇列,該執行緒必須先獲取鎖才行。下面來看下節點的原始碼:

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;	/**	 * Status field, taking on only the values:	 *   SIGNAL:     The successor of this node is (or will soon be)	 *               blocked (via park), so the current node must	 *               unpark its successor when it releases or	 *               cancels. To avoid races, acquire methods must	 *               first indicate they need a signal,	 *               then retry the atomic acquire, and then,	 *               on failure, block.	 *   CANCELLED:  This node is cancelled due to timeout or interrupt.	 *               Nodes never leave this state. In particular,	 *               a thread with cancelled node never again blocks.	 *   CONDITION:  This node is currently on a condition queue.	 *               It will not be used as a sync queue node	 *               until transferred, at which time the status	 *               will be set to 0. (Use of this value here has	 *               nothing to do with the other uses of the	 *               field, but simplifies mechanics.)	 *   PROPAGATE:  A releaseShared should be propagated to other	 *               nodes. This is set (for head node only) in	 *               doReleaseShared to ensure propagation	 *               continues, even if other operations have	 *               since intervened.	 *   0:          None of the above	 *	 * 節點的等待狀態,預設值是0	 */	volatile int waitStatus;	/**	 * 同步佇列的前驅節點	 */	volatile Node prev;	/**	 * 同步佇列的後繼節點	 */	volatile Node next;	/**	 * 當前節點持有的執行緒引用	 */	volatile Thread thread;	/**	 * 條件佇列的後繼節點	 */	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;	}    	// 構造器1,用於初始化頭節點或預設SHARED模式持有鎖	Node() {    	}		// 構造器2	Node(Thread thread, Node mode) {  		//持有模式是賦值給nextWaiter		this.nextWaiter = mode;		this.thread = thread;	}	//構造器3,只在條件佇列中使用	Node(Thread thread, int waitStatus) { 		this.waitStatus = waitStatus;		this.thread = thread;	}}

Node是AQS的內部類,代表了同步佇列和條件佇列中的一個節點。Node有很多屬性,比如鎖的持有模式,等待狀態,同步佇列的前驅和後繼節點,條件佇列的後繼節點等等。

四、AQS的獨佔模式和共享模式

如上面的原始碼,持有鎖的模式分為獨佔模式和共享模式,這兩種模式怎麼理解呢?舉個例子,以餐廳就餐為例,現在是用餐高峰,餐廳只有一間包廂是空閒的,獨佔模式的人比較霸道,

他要麼不進包廂就餐,進來了就不允許別人再進來,自己獨佔整個包廂。共享模式的人就沒那麼講究了,他進來坐下吃飯還不算,還熱情的問問後面的人介不介意一起用餐,如果後面的人不介意那麼就不用再排隊,

大家一起用餐,如果後面的人介意,那就只能排隊等待就餐。

五、節點的等待狀態

上面的原始碼中還有一個等待狀態的變數,這個等待狀態又分為CANCELLED,SIGNAL,CONDITION,PROPAGATE四種狀態。怎麼理解這個狀態呢?

還是以餐廳就餐為例,用餐高峰期,餐廳裡都坐滿了人,再來用餐的人就需要等待有空閒的位置才能就餐。餐廳也很貼心,在餐廳外邊為每一位等待就餐的客人準備了一把椅子用於休息。

等待狀態就可以看作是掛在椅子上的牌子,標識當前椅子上客人的等待狀態。這個牌子的狀態不僅自己可以修改,其他人也能修改。

例如當某個客人在排隊中打算放棄了,他就會將自己椅子上的牌子設為CANCELLED,這樣其他客人看到這個狀態就會將它清理出佇列。

又比如當某個客人在椅子上小憩,他怕自己睡過頭了,就會將前面椅子上的牌子改為SIGNAL,因為每個人在離開排隊區之前都會看一下自己椅子上的牌子,如果牌子上的

狀態為SIGnAL,他就會去喚醒排在他後面的人。只有保證前面椅子上的牌子是SIGNAL,這個客人才能安心小憩。

CONDITION狀態表示執行緒在條件佇列中排隊,PROPAGATE狀態是提醒後面的執行緒可以直接獲取鎖,這個狀態只在共享模式使用到。

六、節點入隊

/** * 將節點插入佇列,必要時進行初始化。 */private Node enq(final Node node) {	for (;;) {	    //獲取同步佇列的尾節點		Node t = tail;		//如果尾節點為空說明同步佇列還未初始化		if (t == null) { 		    //初始化同步佇列,CAS方式設定同步佇列的頭節點			if (compareAndSetHead(new Node()))				tail = head;		} else {			//1.新插入節點的前驅指向當前尾節點			node.prev = t;			//2. CAS方式設定新插入的節點為尾節點			if (compareAndSetTail(t, node)) {				//3. 舊的尾節點的後繼指向當前節點(新的尾節點)				t.next = node;				//返回舊的尾節點				return t;			}		}	}}

從原始碼可知,入隊操作用了一個死迴圈,只有成功將節點插入到同步佇列的尾部,方法才會結束,返回結果是同步佇列舊的尾節點。整個操作過程如下圖:

節點入隊的順序,分為三步:指向尾結點,CAS更改尾結點,將舊尾結點的後繼指向當前結點。在併發環境中這三步操作不一定能保證完成,所以在清空同步佇列所有已取消的結點這一操作中,為了尋找非取消狀態的結點,不是從前向後遍歷而是從後向前遍歷的。還有就是每個結點進入佇列中時它的等待狀態是為0,只有後繼結點的執行緒需要掛起時才會將前面結點的等待狀態改為SIGNAL。這個後續文章會有詳細解讀。

15
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • crontab排程python程式,避免重複排程,應該這樣做