AQS(AbstractQueuedSynchronizer)隊(duì)列同步器源碼閱讀(一)

在看這篇文章之前可以根據(jù)我上一篇文章來對隊(duì)列同步器AQS的應(yīng)用和意義有一個(gè)基礎(chǔ)印象文捶。
http://www.reibang.com/p/e2f339d654f7

從實(shí)現(xiàn)角度分析同步器是如何完成線程同步的壁熄,主要包括:同步隊(duì)列、獨(dú)占式同步狀態(tài)獲取與釋放空免、共享式同步狀態(tài)獲取與釋放以及超時(shí)獲取同步狀態(tài)等同步器的核心數(shù)據(jù)結(jié)構(gòu)與模板方法。

1.同步隊(duì)列

同步器依賴內(nèi)部的同步隊(duì)列(一個(gè)FIFO雙向隊(duì)列)來完成同步狀態(tài)的管理盆耽,當(dāng)前線程獲取同步狀態(tài)失敗時(shí)蹋砚,同步器會將當(dāng)前線程以及等待狀態(tài)等信息構(gòu)造成為一個(gè)節(jié)點(diǎn)(Node)并將其加入同步隊(duì)列,同時(shí)會阻塞當(dāng)前線程摄杂,當(dāng)同步狀態(tài)釋放時(shí)坝咐,會把首節(jié)點(diǎn)中的線程喚醒,使其再次嘗試獲取同步狀態(tài)析恢。

同步隊(duì)列中的節(jié)點(diǎn)(Node)用來保存獲取同步狀態(tài)失敗的線程引用墨坚、等待狀態(tài)以及前驅(qū)和后繼節(jié)點(diǎn)鹃愤,節(jié)點(diǎn)的屬性類型與名稱以及描述如表:

節(jié)點(diǎn)是構(gòu)成同步隊(duì)列的基礎(chǔ)芒涡,同步器擁有首節(jié)點(diǎn)(head)和尾節(jié)點(diǎn)(tail),沒有成功獲取同步狀態(tài)的線程將會成為節(jié)點(diǎn)加入該隊(duì)列的尾部踱蠢,同步隊(duì)列的基本結(jié)構(gòu)如圖:

同步器包含了兩個(gè)節(jié)點(diǎn)類型的引用袖肥,一個(gè)指向頭節(jié)點(diǎn)咪辱,而另一個(gè)指向尾節(jié)點(diǎn)。試想一下椎组,當(dāng)一個(gè)線程成功地獲取了同步狀態(tài)(或者鎖)油狂,其他線程將無法獲取到同步狀態(tài),轉(zhuǎn)而被構(gòu)造成為節(jié)點(diǎn)并加入到同步隊(duì)列中寸癌,而這個(gè)加入隊(duì)列的過程必須要保證線程安全蒸苇,因此同步器提供了一個(gè)基于CAS的設(shè)置尾節(jié)點(diǎn)的方法:compareAndSetTail(Node expect,Node update)庇勃,它需要傳遞當(dāng)前線程“認(rèn)為”的尾節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)罕拂,只有設(shè)置成功后柿菩,當(dāng)前節(jié)點(diǎn)才正式與之前的尾節(jié)點(diǎn)建立關(guān)聯(lián)镜悉。
static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;

       static final int CANCELLED =  1//節(jié)點(diǎn)從同步隊(duì)列中取消
       static final int SIGNAL    = -1//后繼節(jié)點(diǎn)的線程處于等待狀態(tài)稼锅,如果當(dāng)前節(jié)點(diǎn)釋放同步狀態(tài)會通知后繼節(jié)點(diǎn)锥债,使得后繼節(jié)點(diǎn)的線程能夠運(yùn)行允趟;
       static final int CONDITION = -2//當(dāng)前節(jié)點(diǎn)進(jìn)入等待隊(duì)列中
       static final int PROPAGATE = -3//表示下一次共享式同步狀態(tài)獲取將會無條件傳播下去
       static final int INITIAL = 0;//初始狀態(tài)

       volatile int waitStatus //節(jié)點(diǎn)狀態(tài)
       volatile Node prev //當(dāng)前節(jié)點(diǎn)/線程的前驅(qū)節(jié)點(diǎn)
       volatile Node next; //當(dāng)前節(jié)點(diǎn)/線程的后繼節(jié)點(diǎn)
       volatile Thread thread;//加入同步隊(duì)列的線程引用
       Node nextWaiter;//等待隊(duì)列中的下一個(gè)節(jié)點(diǎn)

        final boolean isShared() {
            return nextWaiter == SHARED;
        }
      
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    
        }

        Node(Thread thread, Node mode) {     
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

同步器將節(jié)點(diǎn)加入到同步隊(duì)列的過程如圖:


節(jié)點(diǎn)加入到同步隊(duì)列

節(jié)點(diǎn)加入到同步隊(duì)列同步隊(duì)列遵循FIFO,首節(jié)點(diǎn)是獲取同步狀態(tài)成功的節(jié)點(diǎn),首節(jié)點(diǎn)的線程在釋放同步狀態(tài)時(shí),將會喚醒后繼節(jié)點(diǎn),而后繼節(jié)點(diǎn)將會在獲取同步狀態(tài)成功時(shí)將自己設(shè)置為首節(jié)點(diǎn),該過程如圖:
首節(jié)點(diǎn)的設(shè)置

設(shè)置首節(jié)點(diǎn)是通過獲取同步狀態(tài)成功的線程來完成的斑鼻,由于只有一個(gè)線程能夠成功獲取到同步狀態(tài)荒叶,因此設(shè)置頭節(jié)點(diǎn)的方法并不需要使用CAS來保證,它只需要將首節(jié)點(diǎn)設(shè)置成為原首節(jié)點(diǎn)的后繼節(jié)點(diǎn)并斷開原首節(jié)點(diǎn)的next引用即可。

2.獨(dú)占式同步狀態(tài)獲取與釋放

通過調(diào)用同步器的acquire(int arg)方法可以獲取同步狀態(tài),該方法對中斷不敏感胸蛛,也就是由于線程獲取同步狀態(tài)失敗后進(jìn)入同步隊(duì)列中民珍,后續(xù)對線程進(jìn)行中斷操作時(shí)逆趣,線程不會從同步隊(duì)列中移出痕囱,該方法代碼:

public final void acquire(int arg) {
  if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
}

//tryAcquire(arg)   嘗試以獨(dú)占模式獲取巷查。 該方法應(yīng)該查詢對象的狀態(tài)是否允許以獨(dú)占模式獲取崇败,如果是,則獲取它。
//該方法總是由執(zhí)行獲取的線程調(diào)用予弧。 如果此方法報(bào)告失敗蚓庭,則獲取方法可能將線程排隊(duì)(如果尚未排隊(duì))拳魁,
//直到被其他線程釋放為止贿衍。

 /** 
  *  addWaiter(Node.EXCLUSIVE)  為當(dāng)前線程和給定模式創(chuàng)建和排隊(duì)節(jié)點(diǎn)。
  *  selfInterrupt    Thread.currentThread().interrupt();
  */

主要完成了同步狀態(tài)獲取、節(jié)點(diǎn)構(gòu)造、加入同步隊(duì)列以及在同步隊(duì)列中自旋等待的相關(guān)工作,其主要邏輯是:首先調(diào)用自定義同步器實(shí)現(xiàn)的tryAcquire(int arg)方法,該方法保證線程安全的獲取同步狀態(tài),如果同步狀態(tài)獲取失敗,則構(gòu)造同步節(jié)點(diǎn)(獨(dú)占式Node.EXCLUSIVE,同一時(shí)刻只能有一個(gè)線程成功獲取同步狀態(tài))并通過addWaiter(Node node)该窗。
方法將該節(jié)點(diǎn)加入到同步隊(duì)列的尾部,最后調(diào)用acquireQueued(Node node,int arg)方法蚤霞,使得該節(jié)點(diǎn)以“死循環(huán)”的方式獲取同步狀態(tài)酗失。如果獲取不到則阻塞節(jié)點(diǎn)中的線程,而被阻塞線程的喚醒主要依靠前驅(qū)節(jié)點(diǎn)的出隊(duì)或阻塞線程被中斷來實(shí)現(xiàn)昧绣。

分析一下相關(guān)工作。首先是節(jié)點(diǎn)的構(gòu)造以及加入同步隊(duì)列:

private Node addWaiter(Node mode) {
       // 1. 將當(dāng)前線程構(gòu)建成Node類型
       Node node = new Node(Thread.currentThread(), mode);
       // Try the fast path of enq; backup to full enq on failure
       // 2. 當(dāng)前尾節(jié)點(diǎn)是否為null?
       Node pred = tail;
       if (pred != null) {
           // 2.2 將當(dāng)前節(jié)點(diǎn)尾插入的方式插入同步隊(duì)列中
           node.prev = pred;
           if (compareAndSetTail(pred, node)) {
               pred.next = node;
               return node;
           }
       }
       // 2.1. 當(dāng)前同步隊(duì)列尾節(jié)點(diǎn)為null贪绘,說明當(dāng)前線程是第一個(gè)加入同步隊(duì)列進(jìn)行等待的線程
       enq(node);
       return node;
}

private Node enq(final Node node) {
       for (;;) {
           Node t = tail;
           if (t == null) { // Must initialize
               //1. 構(gòu)造頭結(jié)點(diǎn)
               if (compareAndSetHead(new Node()))
                   tail = head;
           } else {
               // 2. 尾插入,CAS操作失敗自旋嘗試
               node.prev = t;
               if (compareAndSetTail(t, node)) {
                   t.next = node;
                   return t;
               }
           }
       }
}

通過使用compareAndSetTail(Node expect,Node update)方法來確保節(jié)點(diǎn)能夠被線程安全添加.compareAndSetTail該方法由unsafe類提供狸窘,有關(guān)于想要深入了解UnSafe類得墩朦,可以看以下得文章:
https://www.cnblogs.com/throwable/p/9139947.html
如果使用一個(gè)普通的LinkedList來維護(hù)節(jié)點(diǎn)之間的關(guān)系,那么當(dāng)一個(gè)線程獲取了同步狀態(tài)牛哺,而其他多個(gè)線程由于調(diào)用tryAcquire(int arg)方法獲取同步狀態(tài)失敗而并發(fā)地被添加到LinkedList時(shí),LinkedList將難以保證Node的正確添加引润,最終的結(jié)果可能是節(jié)點(diǎn)的數(shù)量有偏差议慰,而且順序也是混亂的蠢古。

在enq(final Node node)方法中,同步器通過“死循環(huán)”來保證節(jié)點(diǎn)的正確添加别凹,在“死循環(huán)”中只有通過CAS將節(jié)點(diǎn)設(shè)置成為尾節(jié)點(diǎn)之后草讶,當(dāng)前線程才能從該方法返回,否則炉菲,當(dāng)前線程不斷地嘗試設(shè)置堕战。可以看出拍霜,enq(final Node node)方法將并發(fā)添加節(jié)點(diǎn)的請求通過CAS變得“串行化”了嘱丢。

節(jié)點(diǎn)進(jìn)入同步隊(duì)列之后,就進(jìn)入了一個(gè)自旋的過程祠饺,每個(gè)節(jié)點(diǎn)(或者說每個(gè)線程)都在自省地觀察越驻,當(dāng)條件滿足,獲取到了同步狀態(tài)道偷,就可以從這個(gè)自旋過程中退出伐谈,否則依舊留在這個(gè)自旋過程中(并會阻塞節(jié)點(diǎn)的線程)

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 1. 獲得當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 2. 當(dāng)前節(jié)點(diǎn)能否獲取獨(dú)占式鎖                  
                // 2.1 如果當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲取同步狀態(tài),即可以獲得獨(dú)占式鎖
                if (p == head && tryAcquire(arg)) {
                    //隊(duì)列頭指針用指向當(dāng)前節(jié)點(diǎn)
                    setHead(node);
                    //釋放前驅(qū)節(jié)點(diǎn)
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 2.2 獲取鎖失敗试疙,線程進(jìn)入等待狀態(tài)等待獲取獨(dú)占式鎖
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
shouldParkAfterFailedAcquire()方法主要邏輯是使用compareAndSetWaitStatus(pred, ws, Node.SIGNAL)使用CAS將節(jié)點(diǎn)狀態(tài)由INITIAL設(shè)置成SIGNAL诵棵,表示當(dāng)前線程阻塞。當(dāng)compareAndSetWaitStatus設(shè)置失敗則說明shouldParkAfterFailedAcquire方法返回false祝旷,然后會在acquireQueued()方法中for (;;)死循環(huán)中會繼續(xù)重試履澳,直至compareAndSetWaitStatus設(shè)置節(jié)點(diǎn)狀態(tài)位為SIGNAL時(shí)shouldParkAfterFailedAcquire返回true時(shí)才會執(zhí)行方法parkAndCheckInterrupt()方法,該方法的源碼為:
private final boolean parkAndCheckInterrupt() {
        //使得該線程阻塞
        LockSupport.park(this);
        return Thread.interrupted();
}

acquireQueued()在自旋過程中主要完成了兩件事情:

如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)怀跛,并且能夠獲得同步狀態(tài)的話距贷,當(dāng)前線程能夠獲得鎖該方法執(zhí)行結(jié)束退出;
獲取鎖失敗的話吻谋,先將節(jié)點(diǎn)狀態(tài)設(shè)置成SIGNAL忠蝗,然后調(diào)用LookSupport.park方法使得當(dāng)前線程阻塞。

在acquireQueued(final Node node,int arg)方法中漓拾,當(dāng)前線程在“死循環(huán)”中嘗試獲取同步狀態(tài)阁最,而只有前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)才能夠嘗試獲取同步狀態(tài),這是為什么骇两?原因有兩個(gè)速种,如下。
第一低千,頭節(jié)點(diǎn)是成功獲取到同步狀態(tài)的節(jié)點(diǎn)配阵,而頭節(jié)點(diǎn)的線程釋放了同步狀態(tài)之后,將會喚醒其后繼節(jié)點(diǎn),后繼節(jié)點(diǎn)的線程被喚醒后需要檢查自己的前驅(qū)節(jié)點(diǎn)是否是頭節(jié)點(diǎn)棋傍。
第二救拉,維護(hù)同步隊(duì)列的FIFO原則。該方法中瘫拣,節(jié)點(diǎn)自旋獲取同步狀態(tài)的行為如圖:


獨(dú)占式同步狀態(tài)獲取流程近上,也就是acquire(int arg)方法調(diào)用流程
獨(dú)占式同步狀態(tài)獲取流程

前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn)且能夠獲取同步狀態(tài)的判斷條件和線程進(jìn)入等待狀態(tài)是獲
取同步狀態(tài)的自旋過程。當(dāng)同步狀態(tài)獲取成功之后拂铡,當(dāng)前線程從acquire(int arg)方法返回壹无,如果對于鎖這種并發(fā)組件而言,代表著當(dāng)前線程獲取了鎖感帅。

當(dāng)前線程獲取同步狀態(tài)并執(zhí)行了相應(yīng)邏輯之后斗锭,就需要釋放同步狀態(tài),使得后續(xù)節(jié)點(diǎn)能夠繼續(xù)獲取同步狀態(tài)失球。通過調(diào)用同步器的release(int arg)方法可以釋放同步狀態(tài)岖是,該方法在釋放了同步狀態(tài)之后,會喚醒其后繼節(jié)點(diǎn)(進(jìn)而使后繼節(jié)點(diǎn)重新嘗試獲取同步狀態(tài))实苞。該方法代碼如:

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

該方法執(zhí)行時(shí)豺撑,會喚醒頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)線程,unparkSuccessor(Node node)方法使用LockSupport來喚醒處于等待狀態(tài)的線程黔牵。

private void unparkSuccessor(Node node) {
    
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)
    Node s = node.next;
    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)
        //后繼節(jié)點(diǎn)不為null時(shí)喚醒該線程
        LockSupport.unpark(s.thread);
}

源碼的關(guān)鍵信息請看注釋聪轿,首先獲取頭節(jié)點(diǎn)的后繼節(jié)點(diǎn),當(dāng)后繼節(jié)點(diǎn)為null的時(shí)候會調(diào)用LookSupport.unpark()方法猾浦,該方法會喚醒該節(jié)點(diǎn)的后繼節(jié)點(diǎn)所包裝的線程陆错。因此,每一次鎖釋放后就會喚醒隊(duì)列中該節(jié)點(diǎn)的后繼節(jié)點(diǎn)所引用的線程金赦,從而進(jìn)一步可以佐證獲得鎖的過程是一個(gè)FIFO(先進(jìn)先出)的過程音瓷。

線程獲取鎖失敗,線程被封裝成Node進(jìn)行入隊(duì)操作夹抗,核心方法在于addWaiter()和enq()绳慎,同時(shí)enq()完成對同步隊(duì)列的頭結(jié)點(diǎn)初始化工作以及CAS操作失敗的重試;

線程獲取鎖是一個(gè)自旋的過程,當(dāng)且僅當(dāng) 當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲得同步狀態(tài)時(shí)漠烧,節(jié)點(diǎn)出隊(duì)即該節(jié)點(diǎn)引用的線程獲得鎖杏愤,否則,當(dāng)不滿足條件時(shí)就會調(diào)用LookSupport.park()方法使得線程阻塞沽甥;
釋放鎖的時(shí)候會喚醒后繼節(jié)點(diǎn)声邦;

總結(jié):在獲取同步狀態(tài)時(shí),同步器維護(hù)一個(gè)同步隊(duì)列摆舟,獲取狀態(tài)失敗的線程都會被加入到隊(duì)列中并在隊(duì)列中進(jìn)行自旋;移出隊(duì)列(或停止自旋)的條件是前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn)且成功獲取了同步狀態(tài)。在釋放同步狀態(tài)時(shí)恨诱,同步器調(diào)用tryRelease(int arg)方法釋放同步狀態(tài)媳瞪,然后喚醒頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)。

可中斷式獲取鎖(acquireInterruptibly方法)

可響應(yīng)中斷式鎖可調(diào)用方法lock.lockInterruptibly();而該方法其底層會調(diào)用AQS的acquireInterruptibly方法照宝,源碼為:

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        //線程獲取鎖失敗
        doAcquireInterruptibly(arg);
}

在獲取同步狀態(tài)失敗后就會調(diào)用doAcquireInterruptibly方法:

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    //將節(jié)點(diǎn)插入到同步隊(duì)列中
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            //獲取鎖出隊(duì)
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //線程中斷拋異常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

與acquire方法邏輯幾乎一致蛇受,唯一的區(qū)別是當(dāng)parkAndCheckInterrupt返回true時(shí)即線程阻塞時(shí)該線程被中斷,代碼拋出被中斷異常厕鹃。

超時(shí)等待式獲取同步狀態(tài)(tryAcquireNanos()方法)

通過調(diào)用lock.tryLock(timeout,TimeUnit)方式達(dá)到超時(shí)等待獲取鎖的效果兢仰,該方法會在三種情況下才會返回:

在超時(shí)時(shí)間內(nèi),當(dāng)前線程成功獲取了鎖剂碴;
當(dāng)前線程在超時(shí)時(shí)間內(nèi)被中斷把将;
超時(shí)時(shí)間結(jié)束,仍未獲得鎖返回false忆矛。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        //實(shí)現(xiàn)超時(shí)等待的效果
        doAcquireNanos(arg, nanosTimeout);
}

通過調(diào)用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超時(shí)獲取同步狀態(tài)察蹲,即在指定的時(shí)間段內(nèi)獲取同步狀態(tài),如果獲取到同步狀態(tài)則返回true催训,否則洽议,返回false。該方法提供了傳統(tǒng)Java同步操作(比如synchronized關(guān)鍵字)所不具備的特性漫拭。

在分析該方法的實(shí)現(xiàn)前亚兄,先介紹一下響應(yīng)中斷的同步狀態(tài)獲取過程。在Java 5之前采驻,當(dāng)一個(gè)線程獲取不到鎖而被阻塞在synchronized之外時(shí)儿捧,對該線程進(jìn)行中斷操作,此時(shí)該線程的中斷標(biāo)志位會被修改挑宠,但線程依舊會阻塞在synchronized上菲盾,等待著獲取鎖。在Java 5中各淀,同步器提供了acquireInterruptibly(int arg)方法懒鉴,這個(gè)方法在等待獲取同步狀態(tài)時(shí),如果當(dāng)前線程被中斷碎浇,會立刻返回并拋出InterruptedException临谱。

超時(shí)獲取同步狀態(tài)過程可以被視作響應(yīng)中斷獲取同步狀態(tài)過程的“增強(qiáng)版”,
doAcquireNanos(int arg,long nanosTimeout)方法在支持響應(yīng)中斷的基礎(chǔ)上奴璃,增加超時(shí)獲取的特性悉默。針對超時(shí)獲取,主要需要計(jì)算出需要睡眠的時(shí)間間隔nanosTimeout苟穆,為了防止過早通知抄课,nanosTimeout計(jì)算公式為:nanosTimeout-=now-lastTime唱星,其中now為當(dāng)前喚醒時(shí)間,lastTime為上次喚醒時(shí)間跟磨,如果nanosTimeout大于0則表示超時(shí)時(shí)間未到间聊,需要繼續(xù)睡眠nanosTimeout納秒,反之抵拘,表示已經(jīng)超超時(shí)哎榴。
代碼:

private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
 //1. 根據(jù)超時(shí)時(shí)間和當(dāng)前時(shí)間計(jì)算出截止時(shí)間
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                //如果獲取到鎖,則把頭節(jié)點(diǎn)出隊(duì)列僵蛛,并返回尚蝌。
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //獲取不到同步狀態(tài),則計(jì)算當(dāng)前時(shí)間充尉,判斷是否超時(shí)飘言,超時(shí)返回false
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
              // 3.線程阻塞等待
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
              //  如果被中斷,拋異常喉酌。
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

該方法在自旋過程中热凹,當(dāng)節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn)時(shí)嘗試獲取同步狀態(tài),如果獲取成功則從該方法返回泪电,這個(gè)過程和獨(dú)占式同步獲取的過程類似般妙,但是在同步狀態(tài)獲取失敗的處理上有所不同。如果當(dāng)前線程獲取同步狀態(tài)失敗相速,則判斷是否超時(shí)(nanosTimeout小于等于0表示已經(jīng)超時(shí))碟渺,如果沒有超時(shí),重新計(jì)算超時(shí)間隔nanosTimeout突诬,然后使當(dāng)前線程等待nanosTimeout納秒(當(dāng)已到設(shè)置的超時(shí)時(shí)間苫拍,該線程會從LockSupport.parkNanos(Object blocker,long nanos)方法返回)。這個(gè)后續(xù)看LockSupport的源碼旺隙。
如果nanosTimeout小于等于spinForTimeoutThreshold(1000納秒)時(shí)绒极,將不會使該線程進(jìn)行超時(shí)等待,而是進(jìn)入快速的自旋過程蔬捷。原因在于垄提,非常短的超時(shí)等待無法做到十分精確,如果這時(shí)再進(jìn)行超時(shí)等待周拐,相反會讓nanosTimeout的超時(shí)從整體上表現(xiàn)得反而不精確铡俐。因此,在超時(shí)非常短的場景下妥粟,同步器會進(jìn)入無條件的快速自旋审丘。


待續(xù)二,休息一下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勾给,一起剝皮案震驚了整個(gè)濱河市滩报,隨后出現(xiàn)的幾起案子锅知,更是在濱河造成了極大的恐慌,老刑警劉巖露泊,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喉镰,死亡現(xiàn)場離奇詭異旅择,居然都是意外死亡惭笑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門生真,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沉噩,“玉大人,你說我怎么就攤上這事柱蟀〈桑” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵长已,是天一觀的道長畜眨。 經(jīng)常有香客問我,道長术瓮,這世上最難降的妖魔是什么康聂? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮胞四,結(jié)果婚禮上恬汁,老公的妹妹穿的比我還像新娘。我一直安慰自己辜伟,他們只是感情好氓侧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著导狡,像睡著了一般约巷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旱捧,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天独郎,我揣著相機(jī)與錄音,去河邊找鬼廊佩。 笑死囚聚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的标锄。 我是一名探鬼主播顽铸,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼料皇!你這毒婦竟也來了谓松?” 一聲冷哼從身側(cè)響起星压,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鬼譬,沒想到半個(gè)月后娜膘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡优质,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年竣贪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巩螃。...
    茶點(diǎn)故事閱讀 38,625評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡演怎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出避乏,到底是詐尸還是另有隱情爷耀,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布拍皮,位于F島的核電站歹叮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铆帽。R本人自食惡果不足惜咆耿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锄贼。 院中可真熱鬧票灰,春花似錦、人聲如沸宅荤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯键。三九已至惹盼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惫确,已是汗流浹背手报。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留改化,地道東北人掩蛤。 一個(gè)月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像陈肛,于是被迫代替她去往敵國和親揍鸟。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 隊(duì)列同步器AbstractQueuedSynchronizer(以下簡稱同步器)句旱,是用來構(gòu)建鎖或者其他同步組件的基...
    CodeKing2017閱讀 1,030評論 0 0
  • Java中的鎖Lock接口隊(duì)列同步器隊(duì)列同步器的接口與示例隊(duì)列同步器的實(shí)現(xiàn)分析同步隊(duì)列獨(dú)占式同步狀態(tài)獲取與釋放共享...
    叫我胖虎大人閱讀 414評論 0 0
  • AQS簡介 AQS:AbstractQueuedSynchronizer阳藻,即隊(duì)列同步器晰奖。它是構(gòu)建鎖或者其他同步組件...
    lijiaccy閱讀 623評論 0 0
  • 基于jdk8 越是核心的東西越是要反復(fù)看,本文篇幅較長腥泥,希望各位細(xì)細(xì)品讀匾南,來回多讀幾遍理解下。 AQS簡介 ??j...
    劍書藏于西閱讀 433評論 0 2
  • ReentrantLock 介紹 一個(gè)可重入的互斥鎖蛔外,它具有與使用{synchronized}方法和語句訪問的隱式...
    tomas家的小撥浪鼓閱讀 4,043評論 1 4