5 AQS

AQS簡介

AQS:AbstractQueuedSynchronizer,即隊列同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等)。

同步器是用來構建鎖和其他同步組件的基礎框架,它的實現主要依賴一個int成員變量來表示同步狀態(tài)以及通過一個FIFO隊列構成等待隊列。它的子類必須重寫AQS的幾個protected修飾的用來改變同步狀態(tài)的方法,其他方法主要是實現了排隊和阻塞機制。AQS使用一個int類型的成員變量state來表示同步狀態(tài),當state>0時表示已經獲取了鎖,當state = 0時表示釋放了鎖。它提供了三個方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))來對同步狀態(tài)state進行操作,當然AQS可以確保對state的操作是安全的。

子類被推薦定義為自定義同步組件的靜態(tài)內部類,同步器自身沒有實現任何同步接口,它僅僅是定義了若干同步狀態(tài)的獲取和釋放方法來供自定義同步組件的使用,同步器既支持獨占式獲取同步狀態(tài),也可以支持共享式獲取同步狀態(tài),這樣就可以方便的實現不同類型的同步組件。

同步器是實現鎖(也可以是任意同步組件)的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義。可以這樣理解二者的關系:鎖是面向使用者,它定義了使用者與鎖交互的接口,隱藏了實現細節(jié);同步器是面向鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態(tài)的管理,線程的排隊,等待和喚醒等底層操作。鎖和同步器很好的隔離了使用者和實現者所需關注的領域。

AQS解決了子類實現同步器時涉及當的大量細節(jié)問題,例如獲取同步狀態(tài)、FIFO同步隊列。基于AQS來構建同步器可以帶來很多好處。它不僅能夠極大地減少實現工作,而且也不必處理在多個位置上發(fā)生的競爭問題。
在基于AQS構建的同步器中,只能在一個時刻發(fā)生阻塞,從而降低上下文切換的開銷,提高了吞吐量。同時在設計AQS時充分考慮了可伸縮行,因此J.U.C中所有基于AQS構建的同步器均可以獲得這個優(yōu)勢。

AQS通過內置的FIFO同步隊列來完成資源獲取線程的排隊工作,如果當前線程獲取同步狀態(tài)失敗(鎖)時,AQS則會將當前線程以及等待狀態(tài)等信息構造成一個節(jié)點(Node)并將其加入同步隊列,同時會阻塞當前線程,當同步狀態(tài)釋放時,則會把節(jié)點中的線程喚醒,使其再次嘗試獲取同步狀態(tài)。

AQS主要提供了如下一些方法:

  • getState():返回同步狀態(tài)的當前值;
  • setState(int newState):設置當前同步狀態(tài);
  • compareAndSetState(int expect, int update):使用CAS設置當前狀態(tài),該方法能夠保證狀態(tài)設置的原子性;
  • tryAcquire(int arg):獨占式獲取同步狀態(tài),獲取同步狀態(tài)成功后,其他線程需要等待該線程釋放同步狀態(tài)才能獲取同步狀態(tài);
  • tryRelease(int arg):獨占式釋放同步狀態(tài);
  • tryAcquireShared(int arg):共享式獲取同步狀態(tài),返回值大于等于0則表示獲取成功,否則獲取失敗;
  • tryReleaseShared(int arg):共享式釋放同步狀態(tài);
  • isHeldExclusively():當前同步器是否在獨占式模式下被線程占用,一般該方法表示是否被當前線程所獨占;
  • acquire(int arg):獨占式獲取同步狀態(tài),如果當前線程獲取同步狀態(tài)成功,則由該方法返回,否則,將會進入同步隊列等待,該方法將會調用可重寫的tryAcquire(int arg)方法;
  • acquireInterruptibly(int arg):與acquire(int arg)相同,但是該方法響應中斷,當前線程為獲取到同步狀態(tài)而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException異常并返回;
  • tryAcquireNanos(int arg,long nanos):超時獲取同步狀態(tài),如果當前線程在nanos時間內沒有獲取到同步狀態(tài),那么將會返回false,已經獲取則返回true;
  • acquireShared(int arg):共享式獲取同步狀態(tài),如果當前線程未獲取到同步狀態(tài),將會進入同步隊列等待,與獨占式的主要區(qū)別是在同一時刻可以有多個線程獲取到同步狀態(tài);
  • acquireSharedInterruptibly(int arg):共享式獲取同步狀態(tài),響應中斷;
  • tryAcquireSharedNanos(int arg, long nanosTimeout):共享式獲取同步狀態(tài),增加超時限制;
  • release(int arg):獨占式釋放同步狀態(tài),該方法會在釋放同步狀態(tài)之后,將同步隊列中第一個節(jié)點包含的線程喚醒;
  • releaseShared(int arg):共享式釋放同步狀態(tài);

CLH同步隊列

AQS內部維護著一個FIFO隊列,該隊列就是CLH同步隊列。

CLH同步隊列是一個FIFO雙向隊列,AQS依賴它來完成同步狀態(tài)的管理,當前線程如果獲取同步狀態(tài)失敗時,AQS則會將當前線程已經等待狀態(tài)等信息構造成一個節(jié)點(Node)并將其加入到CLH同步隊列,同時會阻塞當前線程,當同步狀態(tài)釋放時,會把首節(jié)點喚醒(公平鎖),使其再次嘗試獲取同步狀態(tài)。

在CLH同步隊列中,一個節(jié)點表示一個線程,它保存著線程的引用(thread)、狀態(tài)(waitStatus)、前驅節(jié)點(prev)、后繼節(jié)點(next),其定義如下:

static final class Node {

    /** 共享 */
    static final Node SHARED = new Node();

    /**獨占 */
    static final Node EXCLUSIVE = null;

    /** 因為超時或者中斷,節(jié)點會被設置為取消狀態(tài),被取消的節(jié)點時不會參與到競爭中的,他會一直保持取消狀態(tài)不會轉變?yōu)槠渌麪顟B(tài); */
    static final int CANCELLED =  1;

    /** 后繼節(jié)點的線程處于等待狀態(tài),而當前節(jié)點的線程如果釋放了同步狀態(tài)或者被取消,將會通知后繼節(jié)點,使后繼節(jié)點的線程得以運行 */
    static final int SIGNAL    = -1;
    /** 節(jié)點在等待隊列中,節(jié)點線程等待在Condition上,當其他線程對Condition調用了signal()后,改節(jié)點將會從等待隊列中轉移到同步隊列 */
    static final int CONDITION = -2;
    /** 表示下一次共享式同步狀態(tài)獲取將會無條件地傳播下去  */
    static final int PROPAGATE = -3;

    /** 等待狀態(tài) */
    volatile int waitStatus;

    /** 前驅節(jié)點 */
    volatile Node prev;

    /** 后繼節(jié)點 */
    volatile Node next;

    /** 獲取同步狀態(tài)的線程 */
    volatile Thread thread;

    /**
     * Link to next node waiting on condition, or the special
     * value SHARED.  Because condition queues are accessed only
     * when holding in exclusive mode, we just need a simple
     * linked queue to hold nodes while they are waiting on
     * conditions. They are then transferred to the queue to
     * re-acquire. And because conditions can only be exclusive,
     * we save a field by using special value to indicate shared
     * mode.
     */
    Node nextWaiter;

    /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * Returns previous node, or throws NullPointerException if null.
     * Use when predecessor cannot be null.  The null check could
     * be elided, but is present to help the VM.
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

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

CLH同步隊列入列

addWaiter(Node mode)

private Node addWaiter(Node mode) {
    //新建Node
    Node node = new Node(Thread.currentThread(), mode);
    //快速嘗試添加尾節(jié)點
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        //CAS設置尾節(jié)點
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //多次嘗試
    enq(node);
    return node;
}

enq(node)

private Node enq(final Node node) {
    //死循環(huán)嘗試
    for (;;) {
        Node t = tail;
        if (t == null) { // tail不存在,設置為首節(jié)點
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //設置為尾節(jié)點
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

在上面代碼中,兩個方法都是通過一個CAS方法compareAndSetTail(Node expect, Node update)來設置尾節(jié)點,該方法可以確保節(jié)點是線程安全添加的。在enq(Node node)方法中,AQS通過“死循環(huán)”的方式來保證節(jié)點可以正確添加,只有成功添加后,當前線程才會從該方法返回,否則會一直執(zhí)行下去。

CHL入列過程

CHL出列

CLH同步隊列遵循FIFO,首節(jié)點的線程釋放同步狀態(tài)后,將會喚醒它的后繼節(jié)點(next),而后繼節(jié)點將會在獲取同步狀態(tài)成功時將自己設置為首節(jié)點,這個過程非常簡單,head執(zhí)行該節(jié)點并斷開原首節(jié)點的next和當前節(jié)點的prev即可,注意在這個過程是不需要使用CAS來保證的,因為只有一個線程能夠成功獲取到同步狀態(tài)。過程圖如下:

AQS獨占鎖和共享鎖

獨占鎖

acquire(int arg)方法為AQS提供的模板方法,該方法為獨占式獲取同步狀態(tài),但是該方法對中斷不敏感,也就是說由于線程獲取同步狀態(tài)失敗加入到CLH同步隊列中,后續(xù)對線程進行中斷操作時,線程不會從同步隊列中移除。代碼如下:

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

各個方法定義如下:

  1. tryAcquire:去嘗試獲取鎖,獲取成功則設置鎖狀態(tài)并返回true,否則返回false。該方法自定義同步組件自己實現,該方法必須要保證線程安全的獲取同步狀態(tài)。
  2. addWaiter:如果tryAcquire返回FALSE(獲取同步狀態(tài)失敗),則調用該方法將當前線程加入到CLH同步隊列尾部。
  3. acquireQueued:當前線程會根據公平性原則來進行阻塞等待(自旋),直到獲取鎖為止;并且返回當前線程在等待過程中有沒有中斷過。
  4. selfInterrupt:產生一個中斷。

獨占式同步狀態(tài)獲取

acquireQueued方法為一個自旋的過程,也就是說當前線程(Node)進入同步隊列后,就會進入一個自旋的過程,每個節(jié)點都會自省地觀察,當條件滿足,獲取到同步狀態(tài)后,就可以從這個自旋過程中退出,否則會一直執(zhí)行下去。如下:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        //中斷標志
        boolean interrupted = false;
        /*
         * 自旋過程,其實就是一個死循環(huán)而已
         */
        for (;;) {
            //當前線程的前驅節(jié)點
            final Node p = node.predecessor();
            //當前線程的前驅節(jié)點是頭結點,且同步狀態(tài)成功
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //獲取失敗,線程等待--具體后面介紹
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

從上面代碼中可以看到,當前線程會一直嘗試獲取同步狀態(tài),當然前提是只有其前驅節(jié)點為頭結點才能夠嘗試獲取同步狀態(tài),理由:

  1. 保持FIFO同步隊列原則。
  2. 頭節(jié)點釋放同步狀態(tài)后,將會喚醒其后繼節(jié)點,后繼節(jié)點被喚醒后需要檢查自己是否為頭節(jié)點。


    acquire(int arg)方法流程

獨占式獲取響應中斷

AQS提供了acquire(int arg)方法以供獨占式獲取同步狀態(tài),但是該方法對中斷不響應,對線程進行中斷操作后,該線程會依然位于CLH同步隊列中等待著獲取同步狀態(tài)。為了響應中斷,AQS提供了acquireInterruptibly(int arg)方法,該方法在等待獲取同步狀態(tài)時,如果當前線程被中斷了,會立刻響應中斷拋出異常InterruptedException。

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

首先校驗該線程是否已經中斷了,如果是則拋出InterruptedException,否則執(zhí)行tryAcquire(int arg)方法獲取同步狀態(tài),如果獲取成功,則直接返回,否則執(zhí)行doAcquireInterruptibly(int arg)。doAcquireInterruptibly(int arg)定義如下:

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            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);
    }
}

doAcquireInterruptibly(int arg)方法與acquire(int arg)方法僅有兩個差別。

  1. 方法聲明拋出InterruptedException異常
  2. 在中斷方法處不再是使用interrupted標志,而是直接拋出InterruptedException異常。

獨占式超時獲取

AQS除了提供上面兩個方法外,還提供了一個增強版的方法:tryAcquireNanos(int arg,long nanos)。該方法為acquireInterruptibly方法的進一步增強,它除了響應中斷外,還有超時控制。即如果當前線程沒有在指定時間內獲取同步狀態(tài),則會返回false,否則返回true。如下:

   public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

tryAcquireNanos(int arg, long nanosTimeout)方法超時獲取最終是在doAcquireNanos(int arg, long nanosTimeout)中實現的,如下:

  private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //nanosTimeout <= 0
    if (nanosTimeout <= 0L)
        return false;
    //超時時間
    final long deadline = System.nanoTime() + nanosTimeout;
    //新增Node節(jié)點
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        //自旋
        for (;;) {
            final Node p = node.predecessor();
            //獲取同步狀態(tài)成功
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            /*
             * 獲取失敗,做超時、中斷判斷
             */
            //重新計算需要休眠的時間
            nanosTimeout = deadline - System.nanoTime();
            //已經超時,返回false
            if (nanosTimeout <= 0L)
                return false;
            //如果沒有超時,則等待nanosTimeout納秒
            //注:該線程會直接從LockSupport.parkNanos中返回,
            //LockSupport為JUC提供的一個阻塞和喚醒的工具類,后面做詳細介紹
            if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            //線程是否已經中斷了
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

針對超時控制,程序首先記錄喚醒時間deadline ,deadline = System.nanoTime() + nanosTimeout(時間間隔)。如果獲取同步狀態(tài)失敗,則需要計算出需要休眠的時間間隔nanosTimeout(= deadline - System.nanoTime()),如果nanosTimeout <= 0 表示已經超時了,返回false,如果大于spinForTimeoutThreshold(1000L)則需要休眠nanosTimeout ,如果nanosTimeout <= spinForTimeoutThreshold ,就不需要休眠了,直接進入快速自旋的過程。原因在于 spinForTimeoutThreshold 已經非常小了,非常短的時間等待無法做到十分精確,如果這時再次進行超時等待,相反會讓nanosTimeout 的超時從整體上面表現得不是那么精確,所以在超時非常短的場景中,AQS會進行無條件的快速自旋。

獨占式超時獲取流程圖

獨占式同步狀態(tài)釋放

當線程獲取同步狀態(tài)后,執(zhí)行完相應邏輯后就需要釋放同步狀態(tài)。AQS提供了release(int arg)方法釋放同步狀態(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;
}

該方法同樣是先調用自定義同步器自定義的tryRelease(int arg)方法來釋放同步狀態(tài),釋放成功后,會調用unparkSuccessor(Node node)方法喚醒后繼節(jié)點。

共享鎖

共享式與獨占式的最主要區(qū)別在于同一時刻獨占式只能有一個線程獲取同步狀態(tài),而共享式在同一時刻可以有多個線程獲取同步狀態(tài)。例如讀操作可以有多個線程同時進行,而寫操作同一時刻只能有一個線程進行寫操作,其他操作都會被阻塞。

共享式同步狀態(tài)獲取

AQS提供acquireShared(int arg)方法共享式獲取同步狀態(tài):

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        //獲取失敗,自旋獲取同步狀態(tài)
        doAcquireShared(arg);
}

從上面程序可以看出,方法首先是調用tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài),如果獲取失敗則調用doAcquireShared(int arg)自旋方式獲取同步狀態(tài),共享式獲取同步狀態(tài)的標志是返回 >= 0 的值表示獲取成功。自選式獲取同步狀態(tài)如下:

private void doAcquireShared(int arg) {
    /共享式節(jié)點
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //前驅節(jié)點
            final Node p = node.predecessor();
            //如果其前驅節(jié)點,獲取同步狀態(tài)
            if (p == head) {
                //嘗試獲取同步
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài),返回值為int,當其 >= 0 時,表示能夠獲取到同步狀態(tài),這個時候就可以從自旋過程中退出。

acquireShared(int arg)方法不響應中斷,與獨占式相似,AQS也提供了響應中斷、超時的方法,分別是:acquireSharedInterruptibly(int arg)、tryAcquireSharedNanos(int arg,long nanos),這里就不做解釋了。

阻塞和喚醒線程

在線程獲取同步狀態(tài)時如果獲取失敗,則加入CLH同步隊列,通過通過自旋的方式不斷獲取同步狀態(tài),但是在自旋的過程中則需要判斷當前線程是否需要阻塞,其主要方法在acquireQueued():

if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;

通過這段代碼我們可以看到,在獲取同步狀態(tài)失敗后,線程并不是立馬進行阻塞,需要檢查該線程的狀態(tài),檢查狀態(tài)的方法為 shouldParkAfterFailedAcquire(Node pred, Node node) 方法,該方法主要靠前驅節(jié)點判斷當前線程是否應該被阻塞,代碼如下:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //前驅節(jié)點
    int ws = pred.waitStatus;
    //狀態(tài)為signal,表示當前線程處于等待狀態(tài),直接放回true
    if (ws == Node.SIGNAL)
        return true;
    //前驅節(jié)點狀態(tài) > 0 ,則為Cancelled,表明該節(jié)點已經超時或者被中斷了,需要從同步隊列中取消
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } 
    //前驅節(jié)點狀態(tài)為Condition、propagate
    else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

這段代碼主要檢查當前線程是否需要被阻塞,具體規(guī)則如下:

  1. 如果當前線程的前驅節(jié)點狀態(tài)為SINNAL,則表明當前線程需要被阻塞,調用unpark()方法喚醒,直接返回true,當前線程阻塞
  2. 如果當前線程的前驅節(jié)點狀態(tài)為CANCELLED(ws > 0),則表明該線程的前驅節(jié)點已經等待超時或者被中斷了,則需要從CLH隊列中將該前驅節(jié)點刪除掉,直到回溯到前驅節(jié)點狀態(tài) <= 0 ,返回false
  3. 如果前驅節(jié)點非SINNAL,非CANCELLED,則通過CAS的方式將其前驅節(jié)點設置為SINNAL,返回false

如果 shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true,則調用parkAndCheckInterrupt()方法阻塞當前線程:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

parkAndCheckInterrupt() 方法主要是把當前線程掛起,從而阻塞住線程的調用棧,同時返回當前線程的中斷狀態(tài)。其內部則是調用LockSupport工具類的park()方法來阻塞該方法。

當線程釋放同步狀態(tài)后,則需要喚醒該線程的后繼節(jié)點:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //喚醒后繼節(jié)點
            unparkSuccessor(h);
        return true;
    }
    return false;
}

調用unparkSuccessor(Node node)喚醒后繼節(jié)點:

private void unparkSuccessor(Node node) {
    //當前節(jié)點狀態(tài)
    int ws = node.waitStatus;
    //當前狀態(tài) < 0 則設置為 0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //當前節(jié)點的后繼節(jié)點
    Node s = node.next;
    //后繼節(jié)點為null或者其狀態(tài) > 0 (超時或者被中斷了)
    if (s == null || s.waitStatus > 0) {
        s = null;
        //從tail節(jié)點來找可用節(jié)點
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //喚醒后繼節(jié)點
    if (s != null)
        LockSupport.unpark(s.thread);
}

可能會存在當前線程的后繼節(jié)點為null,超時、被中斷的情況,如果遇到這種情況了,則需要跳過該節(jié)點,但是為何是從tail尾節(jié)點開始,而不是從node.next開始呢?原因在于node.next仍然可能會存在null或者取消了,所以采用tail回溯辦法找第一個可用的線程。最后調用LockSupport的unpark(Thread thread)方法喚醒該線程。

LockSupport

從上面我可以看到,當需要阻塞或者喚醒一個線程的時候,AQS都是使用LockSupport這個工具類來完成的。

LockSupport是用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語

每個使用LockSupport的線程都會與一個許可關聯,如果該許可可用,并且可在進程中使用,則調用park()將會立即返回,否則可能阻塞。如果許可尚不可用,則可以調用 unpark 使其可用。但是注意許可不可重入,也就是說只能調用一次park()方法,否則會一直阻塞。

LockSupport定義了一系列以park開頭的方法來阻塞當前線程,unpark(Thread thread)方法來喚醒一個被阻塞的線程。如下:

public static void unpark(Thread var0) {
    if (var0 != null) {
        UNSAFE.unpark(var0);
    }
}

park(Object blocker)方法的blocker參數,主要是用來標識當前線程在等待的對象,該對象主要用于問題排查和系統(tǒng)監(jiān)控。

park方法和unpark(Thread thread)都是成對出現的,同時unpark必須要在park執(zhí)行之后執(zhí)行,當然并不是說沒有不調用unpark線程就會一直阻塞,park有一個方法,它帶了時間戳(parkNanos(long nanos):為了線程調度禁用當前線程,最多等待指定的等待時間,除非許可可用)。

park()方法的源碼如下:

public static void park() {
    UNSAFE.park(false, 0L);
}

unpark(Thread thread)方法源碼如下:

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

從上面可以看出,其內部的實現都是通過UNSAFE(sun.misc.Unsafe UNSAFE)來實現的,其定義如下:

public native void park(boolean var1, long var2);
public native void unpark(Object var1);

兩個都是native本地方法。Unsafe 是一個比較危險的類,主要是用于執(zhí)行低級別、不安全的方法集合。盡管這個類和所有的方法都是公開的(public),但是這個類的使用仍然受限,你無法在自己的java程序中直接使用該類,因為只有授信的代碼才能獲得該類的實例。

來源:http://cmsblogs.com

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容