<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JUC并發(fā)編程之Semaphore源碼詳解

          共 21235字,需瀏覽 43分鐘

           ·

          2021-07-08 12:09


          點擊上方藍字 關(guān)注我吧



          1
          前言

          在前面分享的一篇文章中,我分享到了ReentrantLock源碼,它是基于AQS進行實現(xiàn)的,那么今天本文分享的同樣也是基于AQS實現(xiàn)的Semaphore工具類


          2
          什么是Semaphore


          Semaphore字面意思是信號量的意思,這種說法并不能夠很好的描述它的作用,更通俗的來說應(yīng)該是令牌管理器。官方文檔說明Semaphore是一個計數(shù)信號量,從概念來講Semaphore包含了一組許可證。每個用戶都必須拿到許可證才能訪問后續(xù)資源,沒有拿到許可證的用于則需要進行等待獲取許可證。


          3
          Semaphore的使用場景


          Semaphore 信號量,用來控制同時訪問資源的線程數(shù),可以通過協(xié)調(diào)各個線程確保合理使用資源。


          這么說會稍微有點不好理解,舉個很通俗易懂例子:

          相信大家伙有在火車站售票窗口排隊買火車票的經(jīng)歷,例如火車站開放了三個售票窗口,現(xiàn)在來了10個人要進行排隊買票,每個窗口同一時刻只能夠處理一個用戶購買火車票,三個售票窗口那么夠同時處理三個用戶購買火車票,后面的人看到售票窗口已經(jīng)有人占用了,也就不能購票了,但是如果前面三個窗口有任意一人購買成功,那么就允許后面一個人進行購票。


          這個案例的人就是線程,而正在購票的操作表示線程正在執(zhí)行業(yè)務(wù)邏輯,離開窗口則表示線程執(zhí)行完畢,看到窗口有人正在購票則排隊等待則表示線程即將入隊阻塞。



          4
          Semaphore源碼詳解


          如何使用Semaphore
          聊完Semaphore的使用場景后,我們來看看基于上面的場景通過Semaphore來實現(xiàn)相應(yīng)的功能
          public class SemaphoreTest {    public static void main(String[] args) {        // 聲明3個窗口        Semaphore windows = new Semaphore(3, true);        for (int i = 0; i < 10; i++) {            new Thread(() -> {                try {                    //占用窗口                    windows.acquire();                    System.out.println(Thread.currentThread().getName() + ":開始買票");                    //模擬買票流程                    Thread.sleep(3000);                    System.out.println(Thread.currentThread().getName() + ":購買成功");                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    windows.release();                }            }).start();        }    }}


          源碼分析

          Semaphore有兩個構(gòu)造方法,默認(rèn)構(gòu)造方法是非公平鎖,而另外一個構(gòu)造方法通過 "fair" 屬性決定是否走公平鎖還是非公平鎖,這個地方其實與ReentrantLock邏輯相似。
          // 默認(rèn)構(gòu)造方法,走非公平鎖流程,permits:支持令牌數(shù)量public Semaphore(int permits) {    sync = new NonfairSync(permits);}// 根據(jù)fair屬性決定是否走公平還是非公平鎖流程,permits:支持令牌數(shù)量public Semaphore(int permits, boolean fair) {    sync = fair ? new FairSync(permits) : new NonfairSync(permits);}


          非公平鎖加鎖流程
          首先從 "windows.acquire()" 作為源碼入口
          public void acquire() throws InterruptedException {    sync.acquireSharedInterruptibly(1);}


          在該方法內(nèi)有兩個if判斷,首先判斷當(dāng)前線程是否被中斷,如果被中斷了則直接拋出異常,這里為什么要這么做呢?放上我的理解,假如當(dāng)前線程被標(biāo)記為中斷狀態(tài)且成功搶到了令牌,但是在執(zhí)行的過程中,該線程有邏輯檢測到線程被中斷從而將線程停止,那么該線程獲取到令牌之后就無法被釋放掉,始終占用著這個令牌,也就意味著后續(xù)都會少一個令牌,而且可能以上這種情況,會導(dǎo)致令牌全部被占用,無法得到釋放。
          而tryAcquireShared()方法則是通用的共享鎖獲取鎖的方法。
          public final void acquireSharedInterruptibly(int arg)        throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    if (tryAcquireShared(arg) < 0)        doAcquireSharedInterruptibly(arg);}


          在我們前面文章中就有講到,在AQS中該方法是個模板方法,具體的加鎖邏輯由子類自身的特性去具體實現(xiàn)的,在Semaphore中,它的加鎖鉤子方法如下所示,如果不進行重寫該方法,則強制拋出異常。
          protected int tryAcquireShared(int arg) {    throw new UnsupportedOperationException();}


          進入到tryAcquireShared()方法,在其內(nèi)部調(diào)用了nonfairTryAcquireShared()非公平鎖方法
          protected int tryAcquireShared(int acquires) {    return nonfairTryAcquireShared(acquires);}


          如果大家伙看過我前面分享ReentranLock源碼,就會發(fā)現(xiàn)這里與一般的tryAcquire邏輯不同,Semaphore的tryAcquire邏輯是一個自旋操作,因為Semaphore它是共享鎖,同一時刻可能存在多個線程來更新這個state值,所以我們需要通過CAS+自旋來保證線程安全,在有令牌的前提下,該方法退出的唯一條件就是通過cas成功更新state值,并返回state剩余值。如果剩下令牌不足(沒有令牌可使用),也就不需要進行cas更新state值,直接返回計算后的state值。 【自旋的目的,關(guān)于這個判斷,如果還有令牌可獲取且通過cas獲取令牌失敗,則繼續(xù)重試獲取令牌】
          final int nonfairTryAcquireShared(int acquires) {    for (;;) { // 自旋        int available = getState();  //獲得當(dāng)前令牌數(shù)量        int remaining = available - acquires; // 計算令牌數(shù)量        if (remaining < 0 ||              compareAndSetState(available, remaining))  // 還有令牌可用的話,則直接進行CAS獲取令牌            return remaining;    }}


          退回到acquireSharedInterruptibly()方法,結(jié)合上面代碼塊流程,如果可用的令牌不足,那么tryAcquireShared()方法返回的內(nèi)容必定是小于0的,為什么呢?其實可以看到上面代碼塊返回的state值是計算后的值,沒有令牌可用state值為0,一旦計算state值后,那么返回的state值為 "-1" ,如果為 "-1" 則進入獲取令牌失敗方法
          public final void acquireSharedInterruptibly(int arg)        throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    if (tryAcquireShared(arg) < 0)        doAcquireSharedInterruptibly(arg);}


          進入到獲取令牌失敗的邏輯方法,主要的邏輯都在自旋里面,但是外面同樣也有個比較重要的方法,就是addWaiter()方法,該方法傳入的參數(shù)值為 "Node.SHARED" ,而SHARED的值就是new Node() 也就是創(chuàng)建了一個空的節(jié)點,然后我們來看看addWaiter()方法其內(nèi)部邏輯做了些什么事情?

          private void doAcquireSharedInterruptibly(int arg)    throws InterruptedException {    final Node node = addWaiter(Node.SHARED);  // 構(gòu)建雙向鏈表 或 入隊操作    boolean failed = true;    try {        for (;;) { // 自旋            final Node p = node.predecessor();  //獲取當(dāng)前節(jié)點的前驅(qū)節(jié)點            if (p == head) {                int r = tryAcquireShared(arg);  // 嘗試獲取令牌                if (r >= 0) {  // 獲取令牌成功                    setHeadAndPropagate(node, r);  //傳播鏈表                    p.next = null; // help GC    將前驅(qū)節(jié)點的引用指向為NULL,待垃圾回收器回收                    failed = false;                    return;  // 獲取令牌成功,退出自旋                }            }            if (shouldParkAfterFailedAcquire(p, node) &&                parkAndCheckInterrupt())   // 阻塞當(dāng)前線程                throw new InterruptedException();        }    } finally {        // 如果某個線程被中斷,非正常流程退出則將當(dāng)前線程的節(jié)點設(shè)置為cancel狀態(tài)        if (failed)            cancelAcquire(node);    }}


          使文字更好理解代碼這里先做前綴說明,node = 當(dāng)前節(jié)點,tail = 鏈表末尾節(jié)點,head = 鏈表頭節(jié)點
          首先將當(dāng)前線程封裝為node節(jié)點,接著獲取tail節(jié)點,判斷當(dāng)前AQS中是否存在雙向鏈表,如果存在的話,將node前驅(qū)節(jié)點引用指向tail節(jié)點,通過cas將node節(jié)點設(shè)置為末尾節(jié)點,如果設(shè)置成功則將tail節(jié)點的后驅(qū)引用指向node,那么node就順理成章的成了雙向鏈表的末尾節(jié)點了。關(guān)于這里我們其實需要思考一個問題,在多線程情況下同時通過cas去設(shè)置尾節(jié)點,此時只會有一個線程設(shè)置成功且返回出去,那接下來的線程該怎么辦呢?且不急,帶著這個疑問我們進入到enq方法
          private Node addWaiter(Node mode) {    Node node = new Node(Thread.currentThread(), mode);   // 封裝節(jié)點    // Try the fast path of enq; backup to full enq on failure    Node pred = tail;  // 獲取末尾節(jié)點    if (pred != null) {        node.prev = pred;   // 當(dāng)前節(jié)點的前驅(qū)引用指向為pred        if (compareAndSetTail(pred, node)) {  // 將當(dāng)前節(jié)點設(shè)置為鏈表末尾節(jié)點            pred.next = node;  // 原末尾節(jié)點后驅(qū)引用指向為當(dāng)前節(jié)點            return node;         }    }    enq(node);    return node;}


          基于FIFO入隊流程圖


          通過如下圖理解上面這段話,我相信應(yīng)該是能夠明白的


          使文字更好理解代碼這里先做前綴說明,node = 當(dāng)前節(jié)點,tail = 鏈表末尾節(jié)點,head = 鏈表頭節(jié)點
          得勒,進來就是一層自旋,注意這里的精華就是自旋,以及上面所提到多線程通過cas設(shè)置尾節(jié)點失敗的解決方案就在此方法。
          進入自旋獲取鏈表的末尾節(jié)點,如果獲取tail為null則證明當(dāng)前并沒有構(gòu)成雙向鏈表,接著通過cas去設(shè)置head,然后將head指向tail,這樣雙向鏈表就完成了,如果獲取tail不為null,將node前驅(qū)引用指向tail節(jié)點,然后tail的后驅(qū)節(jié)點引用指向node節(jié)點,然后返回出去。那如果設(shè)置失敗了怎么辦呢?回到上面的問題,問題不大,這方法不是自旋嘛,它會一直自旋到你設(shè)置成功為止,才退出自旋。
          private Node enq(final Node node) {    for (;;) {        Node t = tail; // 獲取末尾節(jié)點        if (t == null) { // Must initialize   // 構(gòu)建雙向鏈表            if (compareAndSetHead(new Node()))                tail = head;        } else {            node.prev = t;            if (compareAndSetTail(t, node)) {                t.next = node;                return t;            }        }    }}


          如果通過cas設(shè)置不成功,就一直進行自旋,直到設(shè)置成功才退出循環(huán)。


          接著,回到獲取令牌失敗走的邏輯方法,通過上面的流程下來,我們就知道node節(jié)點現(xiàn)在已經(jīng)成功入隊到雙向鏈表中,接著判斷如果當(dāng)前節(jié)點的前驅(qū)節(jié)點是為頭節(jié)點此時會嘗試獲取令牌,如果獲取失敗則將線程進行阻塞,同理當(dāng)前節(jié)點的前驅(qū)節(jié)點不是鏈表的頭節(jié)點,也會將當(dāng)前線程進行阻塞。無論如何只要令牌沒有了,就得老老實實的在隊列中進行呆著,直到下一次的喚醒。
          那如果線程為頭節(jié)點且獲取令牌成功了,setHeadAndPropagate()方法又會做些什么事情呢?帶著這個疑問,我們進去一探究竟
          private void doAcquireSharedInterruptibly(int arg)    throws InterruptedException {    final Node node = addWaiter(Node.SHARED);  // 構(gòu)建雙向鏈表 或 入隊操作    boolean failed = true;    try {        for (;;) { // 自旋            final Node p = node.predecessor();  //獲取當(dāng)前節(jié)點的前驅(qū)節(jié)點            if (p == head) {                int r = tryAcquireShared(arg);  // 嘗試獲取令牌                if (r >= 0) {  // 獲取令牌成功                    setHeadAndPropagate(node, r);  //傳播鏈表                    p.next = null; // help GC    將前驅(qū)節(jié)點的引用指向為NULL,待垃圾回收器回收                    failed = false;                    return;  // 獲取令牌成功,退出自旋                }            }            if (shouldParkAfterFailedAcquire(p, node) && //判斷線程是否需要被阻塞                parkAndCheckInterrupt())   // 阻塞當(dāng)前線程                throw new InterruptedException();        }    } finally {        // 如果某個線程被中斷,非正常流程退出則將當(dāng)前線程的節(jié)點設(shè)置為cancel狀態(tài)        if (failed)            cancelAcquire(node);    }}


          首先我們看到該方法的入?yún)?nèi)容,node:當(dāng)前獲取令牌線程節(jié)點,propagate:剩余令牌數(shù)量。
          該方法主要作用在于兩點,第一點:將當(dāng)前節(jié)點設(shè)置為頭節(jié)點,第二點:將當(dāng)前節(jié)點的獲取令牌成功,則會自動喚醒下一個節(jié)點去獲取令牌,如果獲取令牌失敗了,還是會被加入到隊列進行阻塞
          private void setHeadAndPropagate(Node node, int propagate) {    Node h = head; // Record old head for check below    setHead(node);    /*     * Try to signal next queued node if:     *   Propagation was indicated by caller,     *     or was recorded (as h.waitStatus either before     *     or after setHead) by a previous operation     *     (note: this uses sign-check of waitStatus because     *      PROPAGATE status may transition to SIGNAL.)     * and     *   The next node is waiting in shared mode,     *     or we don't know, because it appears null     *     * The conservatism in both of these checks may cause     * unnecessary wake-ups, but only when there are multiple     * racing acquires/releases, so most need signals now or soon     * anyway.     */    if (propagate > 0 || h == null || h.waitStatus < 0 ||   // 還有令牌可獲取 || 頭節(jié)點狀態(tài)處于等待狀態(tài)        (h = head) == null || h.waitStatus < 0) {        Node s = node.next;  // 獲取當(dāng)前下一節(jié)點        if (s == null || s.isShared())  // 判斷下節(jié)點是否為共享節(jié)點            doReleaseShared();  // 傳播~~ 具體傳播什么呢???    }}


          稍微可以看下設(shè)置頭節(jié)點方法,也就是出隊操作,主要就是將當(dāng)前線程設(shè)置為頭節(jié)點,然后將當(dāng)前節(jié)點的前驅(qū)節(jié)點引用指向為null,配合方法外,會將之前的頭節(jié)點的next節(jié)點設(shè)置為null,那么之前的頭節(jié)點也就自然會被垃圾回收器進行
          private void setHead(Node node) {    head = node;    node.thread = null;    node.prev = null;}


          基于FIFO出隊流程圖


          又一次來到自旋,首先驗證鏈表中是否還存在多個節(jié)點,如果存在且狀態(tài)為SIGNAL會將head的后驅(qū)節(jié)點進行喚醒,讓后驅(qū)節(jié)點嘗試去獲取令牌。如果后驅(qū)節(jié)點獲取失敗了也沒關(guān)系,還是會被阻塞在隊列中,順序是不會變動的。為什么要喚醒后驅(qū)節(jié)點呢?我們可以想象多線程的場景,假如現(xiàn)在可以令牌有兩個,頭節(jié)點獲取令牌成功了,那么還有一個令牌可獲取對吧,恰好我后面還有節(jié)點,我就可以通知下一個節(jié)點繼續(xù)獲取令牌。
          private void doReleaseShared() {    /*     * Ensure that a release propagates, even if there are other     * in-progress acquires/releases.  This proceeds in the usual     * way of trying to unparkSuccessor of head if it needs     * signal. But if it does not, status is set to PROPAGATE to     * ensure that upon release, propagation continues.     * Additionally, we must loop in case a new node is added     * while we are doing this. Also, unlike other uses of     * unparkSuccessor, we need to know if CAS to reset status     * fails, if so rechecking.     */    for (;;) {  // 自旋   可以理解為傳播 【加自旋的原因,可能同時有多個令牌被釋放,那么在這里就可以喚醒后續(xù)所有節(jié)點去獲取令牌,就不用在前面再去判斷是否要去喚醒后驅(qū)節(jié)點了。如果沒有獲取到令牌也沒關(guān)系,后面還是會將沒有搶到的線程進行阻塞住】        Node h = head;          if (h != null && h != tail) {  // 頭節(jié)點不為null 其 頭非等于尾節(jié)點 則證明當(dāng)前鏈表還有多個節(jié)點            int ws = h.waitStatus;   // 獲取head的節(jié)點狀態(tài)            if (ws == Node.SIGNAL) {  // 如果當(dāng)前節(jié)點狀態(tài)為SIGNAL,就代表后驅(qū)節(jié)點正在被阻塞著                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  // 通過cas將狀態(tài)從等待更換為非等待,然后取反的話,將下一個節(jié)點喚醒                    continue;            // loop to recheck cases                unparkSuccessor(h);  // 喚醒線程 去獲取令牌            }            else if (ws == 0 &&                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))  // 如果節(jié)點狀態(tài)已經(jīng)為0,則會將節(jié)點的狀態(tài)更新為PROPAGATE   PROPAGATE:表示下一次共享式同步狀態(tài)獲取將會被無條件地傳播下去                continue;                // loop on failed CAS        }        if (h == head)                   // loop if head changed            break;   // 跳出當(dāng)前循環(huán)    }}


          非公平鎖加鎖流程圖
          那么Semaphore非公平模式獲取令牌的流程到此就結(jié)束啦



          非公平鎖釋放令牌流程
          首先從 "windows.release()" 作為源碼入口
          sync.releaseShared(1);


          我們來看到releaseShared方法,該方法內(nèi)部有兩個核心方法,我們先進入看看tryReleaseShared做了些什么事情
          public final boolean releaseShared(int arg) {    if (tryReleaseShared(arg)) {  //通用釋放令牌        doReleaseShared();  //喚醒后驅(qū)節(jié)點        return true;    }    return false;}

          我們又看到了自旋,獲取令牌數(shù)量并計算令牌數(shù)量,然后通過cas更新到主內(nèi)存中,如果更新失敗繼續(xù)自旋,直到成功才會退出自旋
          protected final boolean tryReleaseShared(int releases) {    for (;;) {        int current = getState();        int next = current + releases;        if (next < current) // overflow            throw new Error("Maximum permit count exceeded");        if (compareAndSetState(current, next))            return true;    }}


          這個方法在前面說過的,又一次來到自旋,首先驗證鏈表中是否還存在多個節(jié)點,如果存在且狀態(tài)為SIGNAL會將head的后驅(qū)節(jié)點進行喚醒,讓后驅(qū)節(jié)點嘗試去獲取令牌。
          private void doReleaseShared() {    /*     * Ensure that a release propagates, even if there are other     * in-progress acquires/releases.  This proceeds in the usual     * way of trying to unparkSuccessor of head if it needs     * signal. But if it does not, status is set to PROPAGATE to     * ensure that upon release, propagation continues.     * Additionally, we must loop in case a new node is added     * while we are doing this. Also, unlike other uses of     * unparkSuccessor, we need to know if CAS to reset status     * fails, if so rechecking.     */    for (;;) {  // 自旋   可以理解為傳播 【加自旋的原因,可能同時有多個令牌被釋放,那么在這里就可以喚醒后續(xù)所有節(jié)點去獲取令牌,就不用在前面再去判斷是否要去喚醒后驅(qū)節(jié)點了。如果沒有獲取到令牌也沒關(guān)系,后面還是會將沒有搶到的線程進行阻塞住】        Node h = head;          if (h != null && h != tail) {  // 頭節(jié)點不為null 其 頭非等于尾節(jié)點 則證明當(dāng)前鏈表還有多個節(jié)點            int ws = h.waitStatus;   // 獲取head的節(jié)點狀態(tài)            if (ws == Node.SIGNAL) {  // 如果當(dāng)前節(jié)點狀態(tài)為SIGNAL,就代表后驅(qū)節(jié)點正在被阻塞著                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  // 通過cas將狀態(tài)從等待更換為非等待,然后取反的話,將下一個節(jié)點喚醒                    continue;            // loop to recheck cases                unparkSuccessor(h);  // 喚醒線程 去獲取令牌            }            else if (ws == 0 &&                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))  // 如果節(jié)點狀態(tài)已經(jīng)為0,則會將節(jié)點的狀態(tài)更新為PROPAGATE   PROPAGATE:表示下一次共享式同步狀態(tài)獲取將會被無條件地傳播下去                continue;                // loop on failed CAS        }        if (h == head)                   // loop if head changed            break;   // 跳出當(dāng)前循環(huán)    }}


          然后我們來看看最終該方法它是如何喚醒后驅(qū)節(jié)點的。
          private void unparkSuccessor(Node node) {    // 先獲取head節(jié)點的狀態(tài),應(yīng)該是等于-1,原因在shouldParkAfterFailedAcquire方法中有體現(xiàn)    int ws = node.waitStatus;     // 由于-1會小于0,所以更新改為0    if (ws < 0)        compareAndSetWaitStatus(node, ws, 0);    // 獲取第一個正常排隊的節(jié)點    Node s = node.next;     //正常解鎖流程不會走該if判斷    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;    }     // 正常來說第一個排隊的節(jié)點不應(yīng)該為空,所以直接把第一個排隊的線程喚醒    if (s != null)        LockSupport.unpark(s.thread);}


          如果線程被喚醒了,則會接著這個方法繼續(xù)執(zhí)行下去
          private final boolean parkAndCheckInterrupt() {    LockSupport.park(this);    return Thread.interrupted();}


          那么到此通過非公平鎖獲取令牌以及釋放令牌就結(jié)束啦,接下來來看看公平鎖是如何實現(xiàn)獲取與釋放令牌的~


          公平鎖獲取令牌流程
          首先從 "windows.acquire()" 作為源碼入口
          public void acquire() throws InterruptedException {    sync.acquireSharedInterruptibly(1);}


          這里與公平鎖獲取令牌方式一摸一樣,就不過多解釋了
          public final void acquireSharedInterruptibly(int arg)        throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    if (tryAcquireShared(arg) < 0)        doAcquireSharedInterruptibly(arg);}


          前面提到了,在AQS中該方法是個模板方法,具體的加鎖邏輯由子類自身的特性去具體實現(xiàn)的,在Semaphore中,它的加鎖鉤子方法如下所示,如果不進行重寫該方法,則強制拋出異常。
          protected int tryAcquireShared(int arg) {    throw new UnsupportedOperationException();}


          接著看獲取鎖的通用方法tryAcquireShared方法,這里有個重點在于,公平鎖的tryAcquireShared()通過自旋+cas獲取令牌之前,會先執(zhí)行hasQueuedPredecessors()方法,不防我們來看看它里面執(zhí)行的邏輯是啥
          protected int tryAcquireShared(int acquires) {    for (;;) {        if (hasQueuedPredecessors())            return -1;        int available = getState();        int remaining = available - acquires;        if (remaining < 0 ||            compareAndSetState(available, remaining))            return remaining;    }}


          從方法名我們就可知道這是判斷隊列中是否有優(yōu)先級更高的等待線程,隊列中哪個線程優(yōu)先級最高?由于頭結(jié)點是當(dāng)前獲取鎖的線程,隊列中的第二個結(jié)點代表的線程優(yōu)先級最高,也就意味著沒有了新來線程插隊的情況,保證了公平鎖的獲取串行化。
          public final boolean hasQueuedPredecessors() {    // The correctness of this depends on head being initialized    // before tail and on head.next being accurate if the current    // thread is first in queue.    Node t = tail; // Read fields in reverse initialization order    Node h = head;    Node s;    return h != t &&        ((s = h.next) == null || s.thread != Thread.currentThread());}


          公平鎖的釋放令牌與非公平鎖的釋放令牌邏輯一摸一樣,也不多解釋啦


          那么到此Semaphore公平與非公平獲取令牌的與釋放令牌的流程,到此就結(jié)束啦


          最后方法Semaphore源碼詳解視頻,可結(jié)合博文一起觀看


          我是黎明大大,我知道我沒有驚世的才華,也沒有超于凡人的能力,但畢竟我還有一個不屈服,敢于選擇向命運沖鋒的靈魂,和一個就是傷痕累累也要義無反顧走下去的心。


          如果您覺得本文對您有幫助,還請關(guān)注點贊一波,后期將不間斷更新更多技術(shù)文章


          掃描二維碼關(guān)注我
          不定期更新技術(shù)文章哦



          JUC并發(fā)編程之ReentrantLock非公平鎖源碼詳解

          JUC并發(fā)編程之Synchronized關(guān)鍵字詳解

          JUC并發(fā)編程之MESI緩存一致協(xié)議詳解

          JUC并發(fā)編程之Volatile關(guān)鍵字詳解

          JUC并發(fā)編程之JMM內(nèi)存模型詳解

          深入Hotspot源碼與Linux內(nèi)核理解NIO與Epoll



          發(fā)現(xiàn)“在看”和“贊”了嗎,因為你的點贊,讓我元氣滿滿哦
          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  老鸭窝永久地址 | 俺也去婷婷官网 | 黄色国产无码 | 天堂国产一区二区三区 | 大鸡巴在线视频网站 |