JUC中的AQS底層詳細超詳解

摘要:當你使用java實現一個線程同步的對象時,一定會包含一個問題:你該如何保證多個線程訪問該對象時 , 正確地進行阻塞等待,正確地被喚醒?
本文分享自華為云社區《JUC中的AQS底層詳細超詳解,剖析AQS設計中所需要考慮的各種問題!》,作者: breakDawn。
java中AQS究竟是做什么的?當你使用java實現一個線程同步的對象時,一定會包含一個問題:
你該如何保證多個線程訪問該對象時,正確地進行阻塞等待,正確地被喚醒?
關于這個問題,java的設計者認為應該是一套通用的機制
因此將一套線程阻塞等待以及被喚醒時鎖分配的機制稱之為AQS
全稱 AbstractQuenedSynchronizer
中文名即抽象的隊列式同步器。
基于AQS,實現了例如ReentenLock之類的經典JUC類 。
AQS簡要步驟
  1. 線程訪問資源 , 如果資源足夠,則把線程封裝成一個Node,設置為活躍線程進入CLH隊列,并扣去資源
  2. 資源不足,則變成等待線程Node,也進入CLH隊列
  3. CLH是一個雙向鏈式隊列, head節點是實際占用鎖的線程 , 后面的節點則都是等待線程所對應對應的節點
AQS的資源statestate定義AQS中的資源是一個int值,而且是volatile的 , 并提供了3個方法給子類使用:
private volatile int state;protected final int getState() { return state;}protected final void setState(int newState) {state = newState;}// cas方法compareAndSetState(int oldState, int newState);如果state上限只有1,那么就是獨占模式Exclusive,例如 ReentrantLock
如果state上限大于1,那就是共享模式Share,例如 Semaphore、CountDownLatch、ReadWriteLock , CyclicBarrier
已經有CAS方法了,為什么資源state還要定義成volatile的?對外暴露的getter/setter方法,是走不了CAS的 。而且setter/getter沒有被synchronized修飾 。所以必須要volatile,保證可見性
這樣基于AQS的實現可以直接通過getter/setter操作state變量,并且保證可見性,也避免重排序帶來的影響 。比如CountDownLatch,ReentrantReadWriteLock,Semaphore都有體現(各種getState、setState)
對資源的操作什么時候用CAS,什么使用setState?volatile的state成員有一個問題 , 就是如果是復合操作的話不能保證復合操作的原子性
因此涉及 state增減的情況,采用CAS
如果是state設置成某個固定值 , 則使用setState
AQS的CLH隊列為什么需要一個CLH隊列這個隊列的目的是為了公平鎖的實現
即為了保證先到先得,要求每個線程封裝后的Node按順序拼接起來 。
CLH本質?是一個Queue容器嗎不是的 , 本質上是一個鏈表式的隊列
因此核心在于鏈表節點Node的定義
JUC中的AQS底層詳細超詳解

文章插圖
除了比較容易想到的prev和next指針外
【JUC中的AQS底層詳細超詳解】還包含了該節點內的線程
以及 waitStatus 等待狀態
4種等待狀態如下:
  • CANCELLED(1): 因為超時或者中斷,節點會被設置為取消狀態 , 被取消的節點時不會參與到競爭中的,他會一直保持取消狀態不會轉變為其他狀態;
  • SIGNAL(-1):后繼節點的線程處于等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知后繼節點 , 使后繼節點的線程得以運行
  • CONDITION(-2) : 點在等待隊列中,節點線程等待在Condition上 , 當其他線程對Condition調用了signal()后,改節點將會從等待隊列中轉移到同步隊列中,加入到同步狀態的獲取中
  • PROPAGATE(-3) : 表示下一次共享式同步狀態獲取將會無條件地傳播下去
  • INIT( 0):
入隊是怎么保證安全的?入隊過程可能引發沖突
因此會用CAS保障入隊安全 。
private Node enq(final Node node) { //多次嘗試,直到成功為止 for (;;) { Node t = tail; //tail不存在 , 設置為首節點 if (t == null) { if (compareAndSetHead(new Node()))tail = head; } else { //設置為尾節點 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }出隊過程會發生什么?一旦有節點出隊,說明有線程釋放資源了 , 隊頭的等待線程可以開始嘗試獲取了 。
于是首節點的線程釋放同步狀態后,將會喚醒它的后繼節點(next)
而后繼節點將會在獲取同步狀態成功時將自己設置為首節點
注意在這個過程是不需要使用CAS來保證的,因為只有一個線程能夠成功獲取到同步狀態

推薦閱讀