java.util.concurrent.locks.AbstractQueuedSynchronizer是J.U.C裏最核心,也是最複雜的一個基礎的類,簡稱AQS。AbstractQueuedSynchronizer是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基礎,因此AbstractQueuedSynchronizer是Lock/Executor實現的前提。
通過以下三個維度來實現:
第一個維度:原子性操作同步器的狀態位
這裏使用一個32位的整數來描述狀態位,(AbstractQueuedSynchronizer類中有個屬性叫:private volatile int state;
),在這裏依然使用CAS操作來解決這個問題。事實上這裏還有一個64位版本的同步器(AbstractQueuedLongSynchronizer,裏面是long state)
第二個維度:阻塞和喚醒線程
JDK1.5之前使用Thread.suspend和Thread.resume實現,屬於過時的api了,也有可能發生死鎖。Jdk1.5之後使用LockSupport類來實現。AQS: AbstractQueuedSynchronizer,就是通過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的。提供的方法比如:
LockSupport.park()
LockSupport.park(Object)
LockSupport.parkNanos(Object,long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(Object,long)
LockSupport.parkUntil(long)
LockSupport.unpark(Thread)
LockSupport是JDK中比較底層的類,是用來創建鎖和其他同步工具類的基本線程阻塞原語。LockSupport 很類似於二元信號量(只有1個許可證可供使用),如果這個許可還沒有被佔用,當前線程獲取許可並繼 續 執行;如果許可已經被佔用,當前線 程阻塞,等待獲取許可。
public static void main(String[] args)
{
LockSupport.park();
System.out.println("block.");
}
運行該代碼,可以發現主線程一直處於阻塞狀態。因爲 許可默認是被佔用的 ,調用park()時獲取不到許可,所以進入阻塞狀態。
如下代碼:先釋放許可,再獲取許可,主線程能夠正常終止。LockSupport許可的獲取和釋放,一般來說是對應的,如果多次unpark,只有一次park也不會出現什麼問題,結果是許可處於可用狀態。
public static void main(String[] args)
{
Thread thread = Thread.currentThread();
LockSupport.unpark(thread);//釋放許可
LockSupport.park();// 獲取許可
System.out.println("b");
}
LockSupport是可不重入 的,如果一個線程連續2次調用 LockSupport .park(),那麼該線程一定會一直阻塞下去。
public static void main(String[] args) throws Exception
{
Thread thread = Thread.currentThread();
LockSupport.unpark(thread);
System.out.println("a");
LockSupport.park();
System.out.println("b");
LockSupport.park();
System.out.println("c");
}
這段代碼打印出a和b,不會打印c,因爲第二次調用park的時候,線程無法獲取許可出現死鎖。
LockSupport可以響應中斷性
public static void t2() throws Exception
{
Thread t = new Thread(new Runnable()
{
private int count = 0;
@Override
public void run()
{
long start = System.currentTimeMillis();
long end = 0;
while ((end - start) <= 1000)
{
count++;
end = System.currentTimeMillis();
}
System.out.println("after 1 second.count=" + count);
//等待或許許可
LockSupport.park();
System.out.println("thread over." + Thread.currentThread().isInterrupted());
}
});
t.start();
Thread.sleep(2000);
// 中斷線程
t.interrupt();
System.out.println("main over");
}
最終線程會打印出thread over.true。這說明 線程如果因爲調用park而阻塞的話,能夠響應中斷請求(中斷狀態被設置成true),但是不會拋出InterruptedException
第三個維度:一個有序的隊列
在AQS中採用CHL列表來解決有序的隊列的問題。
AQS採用的CHL模型採用下面的算法完成FIFO的入隊列和出隊列過程。
對於入隊列(enqueue):採用CAS操作,每次比較尾結點是否一致,然後插入的到尾結點中。
do {
pred = tail;
}while ( !compareAndSet(pred,tail,node) );
對於出隊列(dequeue):由於每一個節點也緩存了一個狀態,決定是否出隊列,因此當不滿足條件時就需要自旋等待,一旦滿足條件就將頭結點設置爲下一個節點。
while (pred.status != RELEASED) ;
head = node;
實際上這裏自旋等待也是使用LockSupport.park()來實現的。
AQS裏面有三個核心字段:
private volatile int state;
private transient volatile Node head;
private transient volatile Node tail;
其中state描述的有多少個線程取得了鎖,對於互斥鎖來說state<=1。head/tail加上CAS操作就構成了一個CHL的FIFO隊列。下面是Node節點的屬性。
volatile int waitStatus; 節點的等待狀態,一個節點可能位於以下幾種狀態:
· CANCELLED = 1: 節點操作因爲超時或者對應的線程被interrupt。節點不應該留在此狀態,一旦達到此狀態將從CHL隊列中踢出。
· SIGNAL = -1: 節點的繼任節點是(或者將要成爲)BLOCKED狀態(例如通過LockSupport.park()操作),因此一個節點一旦被釋放(解鎖)或者取消就需要喚醒(LockSupport.unpack())它的繼任節點。
· CONDITION = -2:表明節點對應的線程因爲不滿足一個條件(Condition)而被阻塞。
· 0: 正常狀態,新生的非CONDITION節點都是此狀態。
· 非負值標識節點不需要被通知(喚醒)。
volatile Node prev;此節點的前一個節點。節點的waitStatus依賴於前一個節點的狀態。
volatile Node next;此節點的後一個節點。後一個節點是否被喚醒(uppark())依賴於當前節點是否被釋放。
volatile Thread thread;節點綁定的線程。
Node nextWaiter;下一個等待條件(Condition)的節點,由於Condition是獨佔模式,因此這裏有一個簡單的隊列來描述Condition上的線程節點。
CLH算法實現
CLH隊列中的結點QNode中含有一個locked字段,該字段若爲true表示該線程需要獲取鎖,且不釋放鎖,爲false表示線程釋放了鎖。結點之間是通過隱形的鏈表相連,之所以叫隱形的鏈表是因爲這些結點之間沒有明顯的next指針,而是通過myPred所指向的結點的變化情況來影響myNode的行爲。CLHLock上還有一個尾指針,始終指向隊列的最後一個結點。CLHLock的類圖如下所示:
當一個線程需要獲取鎖時,會創建一個新的QNode,將其中的locked設置爲true表示需要獲取鎖,然後線程對tail域調用getAndSet方法,使自己成爲隊列的尾部,同時獲取一個指向其前趨的引用myPred,然後該線程就在前趨結點的locked字段上旋轉,直到前趨結點釋放鎖。當一個線程需要釋放鎖時,將當前結點的locked域設置爲false,同時回收前趨結點。如下圖所示,線程A需要獲取鎖,其myNode域爲true,些時tail指向線程A的結點,然後線程B也加入到線程A後面,tail指向線程B的結點。然後線程A和B都在它的myPred域上旋轉,一量它的myPred結點的locked字段變爲false,它就可以獲取鎖掃行。明顯線程A的myPred locked域爲false,此時線程A獲取到了鎖。
整個CLH的代碼如下,其中用到了ThreadLocal類,將QNode綁定到每一個線程上,同時用到了AtomicReference,對尾指針的修改正是調用它的getAndSet()操作來實現的,它能夠保證以原子方式更新對象引用。
CLH鎖也是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,它不斷輪詢前驅的狀態,如果發現前驅釋放了鎖就結束自旋。
importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class CLHLock {
public static class CLHNode {
private boolean isLocked = true; // 默認是在等待鎖
}
@SuppressWarnings("unused" )
private volatile CLHNode tail ;
privatestatic final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER =AtomicReferenceFieldUpdater
. newUpdater(CLHLock.class,CLHNode .class , "tail" );
public void lock(CLHNode currentThreadCLHNode) {
CLHNode preNode = UPDATER.getAndSet( this, currentThreadCLHNode); // 轉載人註釋: 把this裏的"tail" 值設置成currentThreadCLHNode
if(preNode != null) {//已有線程佔用了鎖,進入自旋
while(preNode.isLocked ) {
}
}
}
public void unlock(CLHNode currentThreadCLHNode) {
// 如果隊列裏只有當前線程,則釋放對當前線程的引用(for GC)。
if (!UPDATER .compareAndSet(this, currentThreadCLHNode, null)) {
// 還有後續線程
currentThreadCLHNode. isLocked = false ;// 改變狀態,讓後續線程結束自旋
}
}
}
針對CLS鎖,後期優化出現了MCS鎖。MCSSpinlock 是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,直接前驅負責通知其結束自旋,從而極大地減少了不必要的處理器緩存同步的次數,降低了總線和內存的開銷。
CLH鎖與 MCS鎖的比較
下圖是CLH鎖和MCS鎖隊列圖示:
差異:
- 從代碼實現來看,CLH比MCS要簡單得多。
- 從自旋的條件來看,CLH是在前驅節點的屬性上自旋,而MCS是在本地屬性變量上自旋
- 從鏈表隊列來看,CLH的隊列是隱式的,CLHNode並不實際持有下一個節點;MCS的隊列是物理存在的。
- CLH鎖釋放時只需要改變自己的屬性,MCS鎖釋放則需要改變後繼節點的屬性。
注意:這裏實現的鎖都是獨佔的,且不能重入的。
下面從Semaphore 和CountDownLatch源碼實現來說明AQS:
① Semaphore
semaphore代碼執行的流程,分析acquire的過程
信號量在多線程中有着重要的應用,它的原理是將資源抽象成信號量,如果信號量大於0表明有可用資源,小於0,則需要等待。
對應申請時,就是做信號量減操作;
對應釋放時,就是做信號量加操作。
資源又抽象成state,這是個所有線程都可見的變量。通過state就可以判斷線程是不是可以訪問共享資源了。
1.new Semaphore()對象時,它調用的 sync = new NonfairSync(permits);可以看出它是調用非公平的同步輔助類,其中sync的類型是Sync,它的定義如下:
abstract static class Sync extendsAbstractQueuedSynchronizer{};
2.NonfairSync類又繼承Sync類,它具體實現如下:
final static class NonfairSync extends Sync{
private static finallong serialVersionUID = -2694183684443567898L;
NonfairSync(intpermits) {
super(permits);
/*此處調用的是Sync(intpermits) {
setState(permits);
}
最終state的定義是這樣的private volatile int state;
這個state對所有的線程是可見的,並且是最新的值。
*/
}
//這個是在第三步中的acquireSharedInterruptibly()方法被調用
protected inttryAcquireShared(int acquires) {
returnnonfairTryAcquireShared(acquires);
}
}
3.看看acquire()方法的執行過程
先是調用如下的方法:
public void acquire() throwsInterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly(1) 這個方法調用下面這個方法:
public final voidacquireSharedInterruptibly(int arg) throws InterruptedException {
if(Thread.interrupted())
thrownew InterruptedException();
//如果小於0,表明沒有資源了,就要進入隊列中去
if (tryAcquireShared(arg)< 0)
doAcquireSharedInterruptibly(arg);
}
4.tryAcquireShared()方法調用nonfairTryAcquireShared()方法,看看它的具體是怎樣實現。
final int nonfairTryAcquireShared(intacquires) {
for (;;){
//最新的state值
int available = getState();
//還剩下多少個許可證了(permit)
int remaining = available - acquires;
//只有在還剩有時才更新狀態
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
5.如果沒有許可證了,就要進入隊列中去了,看看doAcquireSharedInterruptibly()這個方法的實現
private voiddoAcquireSharedInterruptibly(int arg)
throwsInterruptedException {
final Node node =addWaiter(Node.SHARED);
/*
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//進入隊列
enq(node);
return node;
}
*/
try {
for (;;){
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
break;
}
} catch (RuntimeExceptionex) {
cancelAcquire(node);
throw ex;
}
// Arrive here only ifinterrupted
cancelAcquire(node);
throw newInterruptedException();
}
② CountDownLatch
CountDownLatch源代碼分析
1.首先分析它的功能:Asynchronization aid that allows one or more threads to wait until a set ofoperations being performed in other threads completes.
也就是說主線程在等待所有其它的子線程完成後再往下執行。常見的例子是待所有的運動員跑完後再排名。
2.它所提供的方法如下:
構造函數:CountDownLatch(intcount)//初始化count數目的同步計數器,只有當同步計數器爲0,主線程纔會向下執行
主要方法:void await()//當前線程等待計數器爲0
boolean await(long timeout,TimeUnit unit)//與上面的方法不同,它加了一個時間限制。
void countDown()//計數器減1
long getCount()//獲取計數器的值
3.它的內部有一個輔助的內部類:sync.
它的實現如下:privatestatic final class Sync extends AbstractQueuedSynchronizer {
private static finallong serialVersionUID = 4982264981922014374L;
/*此處在new CountDownLatch時會調用:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}*/
Sync(int count) {
setState(count);
}
int getCount() {
returngetState();
}
protected inttryAcquireShared(int acquires) {
return(getState() == 0) ? 1 : -1;
}
protected booleantryReleaseShared(int releases) {
//Decrement count; signal when transition to zero
for (;;){
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
4.await()方法的實現
sync.acquireSharedInterruptibly(1);
-->if(tryAcquireShared(arg) < 0)//調用3中的tryAcquireShared()方法
doAcquireSharedInterruptibly(arg);//加入到等待隊列中
5.countDown()方法的實現
sync.releaseShared(1);
--> if(tryReleaseShared(arg))//調用3中的tryReleaseShared()方法
doReleaseShared();//解鎖