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 7class 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多執行緒之JUC包:AbstractQueuedSynchronizer(AQS)原始碼學習筆記
若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: AbstractQueuedSynchronizer(AQS)是一個同步器框架,在實現鎖的時候,一般會實現一個繼承自AQS的內部類sync,作為我們的自定義同步器。AQS內部維護了一個state成員和一個佇列。其中
Java多執行緒之JUC包:Condition原始碼學習筆記
若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: Condition在JUC框架下提供了傳統Java監視器風格的wait、notify和notifyAll相似的功能。 Condition必須被繫結到一個獨佔鎖上使用。ReentrantLock中獲取Conditi
Java多執行緒之JUC包:Semaphore原始碼學習筆記
若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: Semaphore是JUC包提供的一個共享鎖,一般稱之為訊號量。 Semaphore通過自定義的同步器維護了一個或多個共享資源,執行緒通過呼叫acquire獲取共享資源,通過呼叫release釋放。 原始碼:
Java多執行緒之JUC包:CyclicBarrier原始碼學習筆記
若有不正之處請多多諒解,並歡迎批評指正。 請尊重作者勞動成果,轉載請標明原文連結: 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