1. 程式人生 > >Java多執行緒之JUC包:AbstractQueuedSynchronizer(AQS)原始碼學習筆記

Java多執行緒之JUC包:AbstractQueuedSynchronizer(AQS)原始碼學習筆記

若有不正之處請多多諒解,並歡迎批評指正。

請尊重作者勞動成果,轉載請標明原文連結:

AbstractQueuedSynchronizer(AQS)是一個同步器框架,在實現鎖的時候,一般會實現一個繼承自AQS的內部類sync,作為我們的自定義同步器。AQS內部維護了一個state成員和一個佇列。其中state標識了共享資源的狀態,佇列則記錄了等待資源的執行緒。以下這五個方法,在AQS中實現為直接丟擲異常,這是我們自定義同步器需要重寫的方法:

①isHeldExclusively():該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。

②tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗返回false。

③tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗返回false。

④tryAcquireShared(int):共享方式。嘗試獲取資源。成功返回true,失敗返回false。

⑤tryReleaseShared(int):共享方式。嘗試釋放資源,成功則返回true,失敗返回false。

其中isHeldExclusively需要在使用Condition時重寫,他在AQS中的呼叫全部發生在其內部類ConditionObject的方法中。②③和④⑤分別對應了AQS定義的兩種資源共享的方式:Exclusive&share,例如ReentrantLock就是一種獨佔鎖,CountDownLatch和Semaphore是共享鎖。與CountDownLatch有一定相似性的CyclicBarrier並沒有自己的共享同步器,而是使用Lock和Condition來實現的(關於CyclicBarrier的詳解可以參考本人的另一篇博文)。

下面是一個簡單的獨佔鎖的實現,它是不可重入的。它重寫了AQS的tryAcquire方法和tryRelease方法:

 1 import java.io.Serializable;
 2 import java.util.concurrent.TimeUnit;
 3 import java.util.concurrent.locks.AbstractQueuedSynchronizer;
 4 import java.util.concurrent.locks.Condition;
 5 import java.util.concurrent.locks.Lock;
 6 
 7
class Mutex implements Lock, Serializable { 8 //自定義同步器,繼承自AQS 9 private static class Sync extends AbstractQueuedSynchronizer { 10 //試圖獲取鎖,當state為0時能成功獲取, 11 public boolean tryAcquire(int acquires) { 12 assert acquires == 1; //這是一個對於state進行操作的量,含義自定義 13 if (compareAndSetState(0, 1)) { //注意:這是一個原子操作 14 setExclusiveOwnerThread(Thread.currentThread()); 15 return true; 16 } 17 return false; 18 } 19 //釋放鎖,此時state應為1,Mutex處於被獨佔狀態 20 protected boolean tryRelease(int releases) { 21 assert releases == 1; // Otherwise unused 22 if (getState() == 0) throw new IllegalMonitorStateException(); 23 setExclusiveOwnerThread(null); 24 setState(0); 25 return true; 26 } 27 //返回一個Condition 28 Condition newCondition() { return new ConditionObject(); } 29 } 30 31 private final Sync sync = new Sync(); 32 33 public void lock() { sync.acquire(1); } 34 public boolean tryLock() { return sync.tryAcquire(1); } 35 public void unlock() { sync.release(1); } 36 public Condition newCondition() { return sync.newCondition(); } 37 public void lockInterruptibly() throws InterruptedException { 38 sync.acquireInterruptibly(1); 39 } 40 public boolean tryLock(long timeout, TimeUnit unit) 41 throws InterruptedException { 42 return sync.tryAcquireNanos(1, unit.toNanos(timeout)); 43 } 44 }

我們可以看到,利用AQS實現一個簡單的自定義鎖看上去並不複雜,讓我們以此為例,來學習一下AQS的內部原理吧。

一、acquire 獲取鎖

我們先來看一下Mutex重寫的tryAcquire方法:

  //試圖獲取鎖,當state為0時能成功獲取,
     public boolean tryAcquire(int acquires) {
       assert acquires == 1; //這是一個對於state進行操作的量,含義自定義
       if (compareAndSetState(0, 1)) {    //注意:這是一個原子操作
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
     }

注意:當我們初始化一個Sync的時候,如果沒有指定state的初值(無引數),那麼state的預設初值是0。可以看到,方法開頭首先有一個斷言acquires==1,引數acquires代表要在state上做的改變的量(減去或增加),在Mutex中,我們定義state只有兩個狀態:0或1,0代表共享資源可以被獲取,1表示共享資源正在被佔用,因此Mutex是不可重入的。實際上,自定義同步器通過重寫tryAcquire和tryRelease來定義state代表的意義和資源的共享方式,這是同步器的主要任務。Mutex的tryAcquire使用一個原子操作compareAndSetState來試圖獲取資源,這個原子操作由上層的AQS提供,如果成功,將當前執行緒設定為獨佔執行緒並返回true。

Mutex的lock方法呼叫了sync的acquire方法,acquire方法實現為:

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

它首先呼叫tryAquire去獲取共享資源,如果失敗,呼叫addWaiter將當前執行緒放入等待佇列,返回持有當前執行緒的Node物件,然後呼叫acquireQueued方法來監視等待佇列並獲取資源。acquireQueued方法會阻塞,直到成功獲取。注意,acquire方法不能及時響應中斷,只能在成功獲取鎖之後,再來處理。中斷當前執行緒的操作跑出的異常在acquireQueued方法中被捕獲,外部呼叫者沒能看到這個異常,因此呼叫selfInterrupt來重置中斷標識。

我們需要詳細瞭解addWaiter方法和acquireQueued方法,之後再來回顧acquire的過程,才能對整個獲取鎖的流程有比較詳細的瞭解。

我們先來看addWaiter方法:

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

addWaiter首先將當前執行緒包裝在一個Node物件node中,然後獲取了一下佇列的尾節點,如果佇列不為空(tail不為null)的話,呼叫一個CAS函式試圖將node放入等待佇列的尾部,注意,此時可能發生競爭,如果有另外一個執行緒在兩個if之間搶先更新的佇列的尾節點,CAS操作將會失敗,這時會呼叫enq方法,繼續試圖將node放入佇列:

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

enq方法會迴圈檢測佇列,如果佇列為空,則呼叫CAS函式初始化佇列(此時node==head==tail),否則呼叫CAS函式將node放入佇列尾。注意,這兩個CAS是由AQS提供的原子操作。如果CAS失敗,enq會繼續迴圈檢測,直到成功將node入列。enq方法的這種方式有一個專用的名詞:CAS自旋,這種方式在AQS中有多處應用。這裡有一個隱含的知識點,即tail是一個volatile成員,確保某個執行緒更新佇列後對其他執行緒的可見性。

注意:佇列為空的時候,第一個執行緒進入佇列的情況有點tricky:第一個發現佇列為空並初始化佇列(head節點)的執行緒不一定優先拿到資源。head節點被初始化後,當前執行緒需要下一次旋轉才有機會進入佇列,在這期間,完全有可能半路殺出程咬金,將當前執行緒與它初始化出的head節點無情分開。我們來總結一下,當佇列只有一個節點時(head=tail),有兩種情況:第一種是這個佇列剛剛被初始化,head並沒有持有任何執行緒物件。這個狀態不會持續太久,初始化佇列的執行緒有很大機會在下次自旋時把自己接到隊尾。第二種情況是,所有等待執行緒都已經獲得資源並繼續執行下去了,佇列僅有的節點是最後一個獲取共享資源的執行緒,等到下一個執行緒到達等待佇列並將它踢出佇列之後,它才有機會被回收。

enq執行完畢,我們已經成功把當前執行緒放入等待佇列,接下來的任務就是監視佇列,等待獲取資源。這個過程由acquireQueued方法實現:

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                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);
        }
    }

acquireQueued方法是一個很重要的方法,在分析這個方法之前,我們先來說一下AQS中的那個等待佇列。這個佇列實際上是一個CLH佇列,它保證了競爭資源的執行緒按到達順序來獲取資源,避免了飢餓的發生。CLH佇列的工作過程,就是acquireQueued方法的工作過程。很明顯,這又是一個自旋。首先,我們呼叫predecessor方法獲取當前執行緒的前驅節點,如果這個前驅是head節點,就緊接著呼叫tryAcquire去獲取共享資源,當然這是有可能失敗的,因為head節點可能剛剛“上位”,還沒有釋放資源。如果很幸運,我們拿到了資源,就呼叫setHead將node設定為佇列的頭結點,setHead方法同時會將node的prev置為null,緊接著將原先head的next也置為null,顯然這是為了讓其後續被回收。注意:acquireQueued方法在自旋過程中是不可被中斷的,當然它會檢測到中斷(在parkAndCheckInterrupt方法中檢測中斷標誌),但並不會因此結束自旋,只能在獲得資源退出方法後,反饋給上層的方法:我剛剛被中斷了。還記得acquire方法中的selfInterrupt的呼叫嗎,就是為了“補上”這裡沒有響應的中斷。

好,我們繼續往下。獲取資源失敗後(原因有二,head與我之間還有等待執行緒或者head節點的執行緒正在使用資源),呼叫shouldParkAfterFailedAcquire方法檢測是否該去“休息”下,畢竟一直自旋很累嘛。如果可以休息就呼叫parkAndCheckInterrupt放心去休息。我們先來看一下shuldParkAfterFailedAcquire:

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

我們首先了解一下waitStatus。Node物件維護了一個int成員waitStatus,他的可能取值如下:

static final int CANCELLED =  1;
static final int SIGNAL    = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;

下面解釋一下每個值的含義

CANCELLED:因為超時或者中斷,結點會被設定為取消狀態,被取消狀態的結點不應該去競爭鎖,只能保持取消狀態不變,不能轉換為其他狀態。處於這種狀態的結點會被踢出佇列,被GC回收;
SIGNAL:表示這個結點的繼任結點被阻塞了,到時需要通知它;
CONDITION:表示這個結點在條件佇列中,因為等待某個條件而被阻塞;
PROPAGATE:使用在共享模式頭結點有可能處於這種狀態,表示鎖的下一次獲取可以無條件傳播;
0:None of the above,新結點會處於這種狀態。

在我們的Mutex的例子中,節點的waitStatus只可能有CANCELLED、SIGNAL和0三中狀態(事實上,獨佔模式下所有不使用Condition的同步器都是這樣)。

我們繼續來分析shouldParkAfterFailedAcquire方法:

首先檢測下node的前驅節點pred,如果pred狀態已經被置為SIGNAL,直接返回true。否則,從node的前驅繼續往前找,直到找到一個waitStatus小於等於0的節點,設定該點為node的前驅(注意:此時node與這個節點之間的節點從等待佇列中被“摘下”,等待被回收了)並返回false。返回之後,上層的acquireQueued方法繼續自旋,再次進入shouldParkAfterFailedAcquire方法之後,如果發現node前驅不是取消狀態且waitStatus不等於SIGNAL,呼叫CAS函式進行註冊。注意:這個操作可能失敗,因此不能直接返回true,而是返回false由上層的自旋再次呼叫shouldParkAfterFailedAcquire直到確認註冊成功。

歷盡曲折,我們終於可以安心休息了:

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

parkAndCheckInterrupt方法十分簡單,他呼叫LockSupport的靜態方法park阻塞當前執行緒,直到被中斷,這次中斷會被acquireQueued記錄,但不會立即響應,直到自旋完成。注意:返回操作中的interrupted方法會將中斷標誌復位,因此我們在上層需要將這個中斷“補上”,再一次:還記得大明湖邊的selfInterrupt嗎?

二、release 釋放鎖

我們先來看一下Mutex中重寫的tryRelease方法:

     //釋放鎖,此時state應為1,Mutex處於被獨佔狀態
     protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (getState() == 0) throw new IllegalMonitorStateException();
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

邏輯比較簡單,首先將獨佔執行緒置為null,緊接著將state設定為0,這裡不會發生資源競爭,因此不需要用CAS去設定state值,直接置0即可。

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

好,我們開始分析release方法。首先呼叫tryRelease試圖釋放共享資源,緊接著檢測自己的waitStatus是否為SIGNAL,如果是的話,呼叫unparkSuccessor喚醒佇列中的下一個執行緒。獨佔模式下,waitStatus!=0與waitStatus==-1等價(這裡waitStatus不會為CANCELLED,因為已經獲取資源了)。如果不為SIGNAL,說明如果有下個等待執行緒,它正在自旋。所以直接返回true即可。我們來看下unparkSuccessor方法:

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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)
            LockSupport.unpark(s.thread);
    }

unparkSuccessor方法也會在共享模式的工作流程中被呼叫,因此方法開始做的判斷是有必要的。對於獨佔模式而言,ws應該都是0。然後找到下一個需要被喚醒的執行緒並呼叫LockSupport的靜態方法unpark喚醒等待執行緒。

至此,我們比較詳細地瞭解了acquire&release的工作流程。

三、acquireShared 獲取鎖

下面,我們來學習下共享模式下的獲取&釋放鎖的工作流程。

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

acquireShared方法首先呼叫tryAcquireShared試圖獲取共享資源。tryAcquireShared的返回值表示剩餘資源個數,負值表示獲取失敗,0表示獲取成功但已無剩餘資源。如果獲取失敗,呼叫doAcquireShared方法完成獨佔模式下類似的操作,後面我們會詳細分析。注意,doAcquireShared方法在等待資源的過程中也是不響應中斷的,它能覺察到中斷,但在成功獲取資源之前不會處理。

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                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);
        }
    }

doAcquireShared方法與acquireQueued方法相似,不同的地方在於,共享模式下成功獲取資源並將head指向自己之後,要檢查並試圖喚醒之後的等待執行緒。因為共享資源可能剩餘,可以被後面的等待執行緒獲取。

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

setHeadAndPropagate中有一個長長的if,來判斷是否應該去試圖喚醒後面的執行緒。其中h==null的判斷筆者始終不能理解,因為檢視程式碼發現,之後佇列尚未初始化的時候為空,後續都不可能為空了。關於這點希望各位看官不吝指教。其他情況,propagate大於0,表示尚有資源可被獲取,顯然應該繼續判斷;而當h.waitStatus小於0時,它有兩種取值可能,SIGNAL和PROPAGATE,我們將在後面看到,這兩種情況都是應該繼續判斷。後續是對node的後繼進行的判斷,注意,node此時可能已經不是head節點了,因為這是共享模式,所以可能有一個node的後繼成功獲取資源後,把自己設為head,將node踢出了佇列。這種情況下node的後繼s是可能為null的,但貌似這種情況doReleaseShared的呼叫沒有意義。s.isShared的判斷主要是考慮到讀寫鎖的情況,在讀寫鎖的使用過程中,申請寫鎖(獨佔模式)和申請讀鎖(共享模式)的執行緒可能同時存在,這個判斷髮現後即執行緒是共享模式的時候,呼叫soReleaseShared方法喚醒他。

但總之,我們十分保守謹慎地呼叫了doReleaseShared方法試圖喚醒後繼執行緒:

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

又是一個自旋。我們首先獲取head節點h,然後檢查它的waitStatus是否為SIGNAL,如果是的話,呼叫CAS將h的waitStatus設定為0,並呼叫unparkSuccessor喚醒下一個等待執行緒。注意,這裡呼叫CAS方法而不是直接賦值,是因為在共享模式下,這裡可能發生競爭。doReleaseShared方法可能由head節點在使用完共享資源後主動呼叫(後續在releaseShared方法中可以看到),也可能由剛剛“上位”的等待執行緒呼叫,在上位之後,原來的head執行緒已被踢出佇列。

因此,doReleaseShared方法的執行情況變得比較複雜,需要細緻分析。

第一種情況,只有剛剛釋放資源的head執行緒呼叫,這時候沒有競爭,waitStatus是SIGNAL,就去喚醒下個執行緒,是0,就重置為PROPAGATE。

第二種情況,剛剛釋放完資源的舊head,和剛剛上位的新head同時呼叫doReleaseShared方法,這時候最新的head獲取的都是自己,若干被踢出的舊head獲取的可能是舊head,也可能是新head,這些被踢出的舊head執行緒也在根據自己獲取的head(不管新舊)的狀態進行CAS操作和unparkSuccessor操作,幸運的是(必須幸運啊。。),這些操作不會造成錯誤,只是多了一些喚醒而已(這些喚醒可能導致一個執行緒獲得資源,也可能是一個“虛晃”)。

我們可以發現,不管head引用怎樣更迭,最終新head的waitStatus都會被順利處理。注意,可能有多箇舊head同時參與這個過程,都不影響正確性。

我們注意到,一個新head,在他剛上位的時候有機會呼叫一次setHeadAndPropagate進而呼叫doReleaseShared,在他釋放資源之後,又一次呼叫doReleaseShared(這次是必然的)。第一次呼叫時,不管新head的waitStatus是0還是SIGNAL,最終狀態都被PROPAGATE(當然,被踢出佇列的head可能還沒來得及設定成PROPAGATE,但新上位的head最終會被設定),這也符合PROPAGATE的語義:使用在共享模式頭結點有可能處於這種狀態,表示鎖的下一次獲取可以無條件傳播。

還有一個問題,它是由SIGNAL-->0-->PROPAGATE變化而來的,為什麼不是SIGNAL-->PROPAGA這樣直接變化呢?原因是unparkSuccessor方法會試圖將當前node的waitStatus復位成0,如果我們直接SIGNAL-->PROPAGA後,那麼又被複位成0,還需要一次CAS操作置為PROPAGATE。

四、releaseShared 釋放鎖

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

我們可以看到,呼叫tryReleaseShared成功釋放共享資源之後,最終要再次呼叫doReleaseShared試圖喚醒後面的等待執行緒。

五、ConditionObject

關於Condition的內容請看筆者的另一篇博文。

至此,我們對獨佔模式和共享模式下、不響應中斷的、沒有等待時間引數的獲取資源和釋放資源的流程有了初步瞭解。這時去看JUC包中的鎖的原始碼,相信會有更深的理解。

相關推薦

Java執行JUCAbstractQueuedSynchronizerAQS原始碼學習筆記

若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: AbstractQueuedSynchronizer(AQS)是一個同步器框架,在實現鎖的時候,一般會實現一個繼承自AQS的內部類sync,作為我們的自定義同步器。AQS內部維護了一個state成員和一個佇列。其中

Java執行JUCCondition原始碼學習筆記

若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: Condition在JUC框架下提供了傳統Java監視器風格的wait、notify和notifyAll相似的功能。 Condition必須被繫結到一個獨佔鎖上使用。ReentrantLock中獲取Conditi

Java執行JUCSemaphore原始碼學習筆記

若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: Semaphore是JUC包提供的一個共享鎖,一般稱之為訊號量。 Semaphore通過自定義的同步器維護了一個或多個共享資源,執行緒通過呼叫acquire獲取共享資源,通過呼叫release釋放。 原始碼:

Java執行JUCCyclicBarrier原始碼學習筆記

若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: CyclicBarrier是java.util.concurrent包中提供的同步工具。通過這個工具我們可以實現n個執行緒相互等待。我們可以通過引數指定達到公共屏障點之後的行為。 先上原始碼:

Java執行知識點總結——進階篇 等待喚醒機制 Lock 鎖升級版

JDK1.5 中提供了多執行緒升級解決方案。 將同步 Synchronized 替換成現實 Lock 操作。 將Object中的 wait、notify、notifyAll,替換成了C

Java執行知識點總結——進階篇執行下的單例模式

餓漢式 餓漢式多執行緒和單執行緒的程式碼是一樣的,如下: class Single { private static final Single s = new Single(); p

執行記憶體可見性Volatile

從這篇博文開始,我們開始分享一些多執行緒的內容,畢竟在工作中,使用多執行緒比較多。多總結一下,終歸沒有壞處。這個系列的文章不會特別長,爭取在3到5分鐘之間結束,主要以說明白內容,給出相應的解決方案,重點在於實踐。 如標題所示,這篇博文我們簡單的介紹一下記憶體可

執行原子變數CAS演算法

上篇博文,我們介紹了多執行緒之記憶體可見性Volatile(一),但是也遺留了一個問題,如何保證變數的”原子性操作(Atomic operations)”? Volatile保證部分型別的原子性 上篇博文,我們說Voloatile不能保證原子性,有一點侷

JAVA執行和併發基礎面試問答轉載

  多執行緒和併發問題是Java技術面試中面試官比較喜歡問的問題之一。在這裡,從面試的角度列出了大部分重要的問題,但是你仍然應該牢固的掌握Java多執行緒基礎知識來對應日後碰到的問題。(校對注:非常贊同這個觀點) Java多執行緒面試問題 1. 程序和執行緒之間有什麼不同? 一個程序是一個獨立(

java執行與高併發庫應用執行建立和定時任務Timer

1、建立執行緒的兩種方式, 通過start, 執行run方法。 第一種實現runnable, 定義類實現Runnable介面 重寫Runnable介面中的run方法 通過Thread建立執行緒物件 將Runnable介面的子類物件作為實際引數傳遞

Java執行系列--“JUC原子類”03 AtomicLong原子類

轉自:https://www.cnblogs.com/skywang12345/p/3514593.html(含部分修改) 概要 AtomicInteger, AtomicLong和AtomicBoolean這3個基本型別的原子類的原理和用法相似。本章以AtomicLong對基本型別的原子類進行介紹。內容

Java執行系列---“JUC原子類”04 AtomicLongArray原子類

轉自:https://www.cnblogs.com/skywang12345/p/3514604.html(含部分修改) 概要 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray這3個數組型別的原子類的原理和用法相似。本章以AtomicLo

Java執行系列---“JUC原子類”05 AtomicReference原子類

轉自:http://www.cnblogs.com/skywang12345/p/3514623.html(部分修改) 概要 本章對AtomicReference引用型別的原子類進行介紹。內容包括: AtomicReference介紹和函式列表 AtomicReference原始碼分析(基於J

Java執行系列---“JUC原子類”06 AtomicLongFieldUpdater原子類

轉自:http://www.cnblogs.com/skywang12345/p/3514635.html (含部分修改) 概要 AtomicIntegerFieldUpdater, AtomicLongFieldUpdater和AtomicReferenceFieldUpdater這3個修改類的成員的原

Java執行系列---“JUC原子類”01 原子類的實現CAS演算法

轉自:https://blog.csdn.net/ls5718/article/details/52563959  & https://blog.csdn.net/mmoren/article/details/79185862(含部分修改)   在JDK 5之前Java語言是靠

Java執行系列---“JUC原子類”02 框架

轉自:http://www.cnblogs.com/skywang12345/p/3514589.html   根據修改的資料型別,可以將JUC包中的原子操作類可以分為4類。 1. 基本型別: AtomicInteger, AtomicLong, AtomicBoolean ;2.&

Java執行系列---“JUC鎖”01 框架

轉自:http://www.cnblogs.com/skywang12345/p/3496098.html(含部分修改)   本章,我們介紹鎖的架構;後面的章節將會對它們逐個進行分析介紹。目錄如下: 01. Java多執行緒系列--“JUC鎖”01之 框架 02. 

Java執行系列---“JUC鎖”02 ReentrantLock

轉自:https://www.jianshu.com/p/96c89e6e7e90 & https://blog.csdn.net/Somhu/article/details/78874634 (含部分修改) 一.ReentrantLock鎖 1.Lock介面 Lock,鎖

Java執行系列---“JUC鎖”06 公平鎖

轉自:http://www.cnblogs.com/skywang12345/p/3496609.html 釋放公平鎖(基於JDK1.7.0_40) 1. unlock() unlock()在ReentrantLock.java中實現的,原始碼如下: public void unlock() {

Java執行系列--“JUC執行池”01 執行池架構

概要 前面分別介紹了”Java多執行緒基礎”、”JUC原子類”和”JUC鎖”。本章介紹JUC的最後一部分的內容——執行緒池。內容包括: 執行緒池架構圖 執行緒池示例 執行緒池架構圖 執行緒池的架構圖如下: 1、Executor