java多線程進階學習1
前言
本章學習知識點
- ThreadLocal
- AQS
- 併發工具類及線程池
1. ThreadLocal
爲了解釋ThreadLocal類的工作原理,必須同時介紹與其工作甚密的其他幾個類
- ThreadLocalMap
- Thread
在Thread類存在一行代碼如下:
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap類的定義是在ThreadLocal類中,真正的引用卻是在Thread類中。
ThreadLocal中set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 設置ThreadLocal時看了初始化了內部類
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
從上面源碼可以看到ThreadLocalMap中用於存儲數據的entry定義:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
接下來在看下ThreadLocalMap的set方法,從中我們可以發現這個Map的key是ThreadLocal類的實例對象hash值和map的length做與運算,value爲用戶的值
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocal中get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLoacl小結
-
Thread類中有一個成員變量屬於ThreadLocalMap類(一個定義在ThreadLocal類中的內部類),它是一個Map,他的key是ThreadLocal實例對象。
-
當爲ThreadLocal類的對象set值時,首先獲得當前線程的ThreadLocalMap類屬性,然後以ThreadLocal類的對象爲key,設定value。get值時則類似。
-
ThreadLocal變量的活動範圍爲某線程,是該線程“專有的,獨自霸佔”的,對該變量的所有操作均由該線程完成!也就是說,ThreadLocal 不是用來解決共享對象的多線程訪問的競爭問題的,因爲ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。當線程終止後,這些值會作爲垃圾回收。
-
由ThreadLocal的工作原理決定了:每個線程獨自擁有一個變量,並非是共享的。
Tip: 如果把一個共享的對象直接保存到ThreadLocal中,那麼多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有併發訪問問題。 所以要在保存到ThreadLocal之前,通過克隆或者new來創建新的對象,然後再進行保存。
ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命週期內起作用。作用:提供一個線程內公共變量(比如本次請求的用戶信息),減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的複雜度,或者爲線程提供一個私有的變量副本,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產生影響。
ThreadLocal的內存泄露問題
從ThreadLocalMap中的Entry方法的源碼(WeakReference),我們知道ThreadLocalMap是使用ThreadLocal的弱引用作爲Key的。如果一個ThreadLocal沒有外部強引用引用他,那麼系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,如果當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永遠無法回收,造成內存泄露。
ThreadLocalMap設計時的對上面問題的對策:
ThreadLocalMap的getEntry函數爲例
- 首先從ThreadLocal的直接索引位置(通過ThreadLocal.threadLocalHashCode & (table.length-1)運算得到)獲取Entry e,如果e不爲null並且key相同則返回e;
- 如果e爲null或者key不一致則向下一個位置查詢,如果下一個位置的key和當前需要查詢的key相等,則返回對應的Entry。否則,如果key值爲null,則擦除該位置的Entry,並繼續向下一個位置查詢。在這個過程中遇到的key爲null的Entry都會被擦除,那麼Entry內的value也就沒有強引用鏈,自然會被回收。仔細研究代碼可以發現,set操作也有類似的思想,將key爲null的這些Entry都刪除,防止內存泄露。
但是光這樣還是不夠的,上面的設計思路依賴一個前提條件:要調用ThreadLocalMap的getEntry函數或者set函數。這當然是不可能任何情況都成立的,所以很多情況下需要使用者手動調用ThreadLocal的remove函數,手動刪除不再需要的ThreadLocal,防止內存泄露。所以JDK建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命週期就更長,由於一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據ThreadLocal的弱引用訪問到Entry的value值,然後remove它,防止內存泄露
順便提一句:ThreadLocalMap內部類初始容量16,負載因子2/3,解決衝突的方法是再hash法,也就是:在當前hash的基礎上再自增一個常量。
2. AQS(AbstractQueuedSynchronizer抽象的隊列式的同步器)
https://blog.csdn.net/CringKong/article/details/80560937
https://www.cnblogs.com/iou123lg/p/9464385.html
https://www.cnblogs.com/doit8791/p/10971420.html
https://www.cnblogs.com/tong-yuan/p/AbstractQueuedSynchronizer.html
2.1 AQS設計思路
CAS操作+volatile關鍵字+改造的CLH隊列
首先AQS內部維護了一個變形的CLH隊列,一個基於AQS實現的同步器,這個同步器管理的所有線程,都會被包裝成一個結點,進入隊列中,所有線程結點,共享AQS中的state(同步狀態碼)。
AQS中的state
狀態碼是一個volatile
變量,**而對狀態碼的修改操作,全部都是CAS操作,這樣就保證了多線程間對狀態碼的同步性,**這種方式也是我們之前所說的CAS常用的操作模式。
核心方法是acquire和release
-
acquire
while (當前同步器的狀態不允許獲取操作) {
如果當前線程不在隊列中,則將其插入隊列
阻塞當前線程
}
-
release
if (新的狀態允許某個被阻塞的線程獲取成功)
解除隊列中一個或多個線程的阻塞狀態
-
三大關鍵操作:同步器的狀態變更、線程阻塞和釋放、插入和移出隊列。
-
三個基本組件:同步器狀態的原子性管理 ,線程阻塞與解除阻塞 ,隊列的管理
2.1.1 state的訪問方式
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
2.2. 2 AQS的隊列
整個框架的核心就是如何管理線程阻塞隊列,該隊列是嚴格的FIFO隊列,因此不支持線程優先級的同步。同步隊列的最佳選擇是自身沒有使用底層鎖來構造的非阻塞數據結構,業界主要有兩種選擇,一種是MCS鎖,另一種是CLH鎖。其中CLH一般用於自旋,但是相比MCS,CLH更容易實現取消和超時,所以同步隊列選擇了CLH作爲實現的基礎。
最好先了解下CLH隊列的算法.
private transient volatile Node head;
private transient volatile Node tail;
下面是內部類Node源碼
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// voatile屬性
volatile int waitStatus;
volatile Node prev; // 上一個節點的指針
volatile Node next; // 下一個節點的指針
volatile Thread thread;
Node nextWaiter;
// 是否是共享
final boolean isShared() {
return nextWaiter == SHARED;
}
// 取下一個節點
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;
}
}
變種的CLH隊列,CLH隊列其實就是採用了狀態代理布爾值。根據不同的狀態來決定線程的不同行爲。
2.2 獨佔模式
一個時間段內,只能有一個線程可以操作共享資源,這就是獨佔模式。
AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個線程能執行,如ReentrantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)。
AQS中有四個核心的頂層入口方法:
acquire(int)、release(int)、acquireShared(int)、releaseShared(int)
以AQS爲基礎實現的同步器類,只需要合理使用這些方法,就可以實現需要的功能。
顯而易見:acquire(int)、release(int)是獨佔模式中的方法。
而acquireShared(int)、releaseShared(int)是共享模式中的方法。
入隊操作:CLH是FIFO隊列,每次加入隊列的新元素都會放在當前尾節點後面.如果某一個線程獲取了同步狀態,其他線程就都處於自旋狀態,那麼新增線程就必須是線程安全的操作,不然無法保證順序性,因此增加節點採用CAS操作.
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
// 併發處理 尾節點有可能已經不是之前的節點 所以需要CAS更新
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 第一個入隊的節點或者是尾節點後續節點新增失敗時進入enq
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
一個死循環,本身沒有鎖,可以多個線程併發訪問
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 第一次循環執行,此時head, tail都爲null,用CAS的方式創建一個空的Node作爲頭結點
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 。將當前線程的Node結點(簡稱CNode)的prev指向tail,然後使用CAS將tail指向CNode
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
節點進入同步隊列之後,就進入了一個自旋的過程,每個線程節點都在自省地觀察,當條件滿足,獲取到了同步狀態,就可以從這個自旋過程中退出,否則依舊留在這個自旋過程中並會阻塞節點的線程
// *************** 獲取鎖 *************************
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);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前驅節點的同步狀態
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
前驅節點爲-1 後續節點被阻塞
* 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;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞線程
return Thread.interrupted();
}
// *************** 釋放鎖 *************************
public final boolean release(int arg) {
if (tryRelease(arg)) {//同步狀態釋放成功
Node h = head;
if (h != null && h.waitStatus != 0)
//直接釋放頭節點
unparkSuccessor(h);
return true;
}
return false;
}
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);
}
總結下獨佔式同步狀態的獲取和釋放:在獲取同步狀態時,同步器維護一個同步隊列,獲取狀態失敗的線程都會被加入到隊列中並在隊列中進行自旋;移出隊列的條件是前驅節點爲頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器調用tryRelease方法釋放同步狀態,然後喚醒頭節點的後繼節點。
獨佔流程:
獲取獨佔鎖流程:
調用入口方法acquire(arg)
調用模版方法tryAcquire(arg)嘗試獲取鎖,若成功則返回,若失敗則走下一步
將當前線程構造成一個Node節點,並利用CAS將其加入到同步隊列到尾部,然後該節點對應到線程進入自旋狀態
自旋時,首先判斷其前驅節點是否爲頭節點&是否成功獲取同步狀態,兩個條件都成立,則將當前線程的節點設置爲頭節點,如果不是,則利用
LockSupport.park(this)
將當前線程掛起 ,等待被前驅節點喚醒釋放獨佔鎖流程:
調用入口方法release(arg)
調用模版方法tryRelease(arg)釋放同步狀態
獲取當前節點的下一個節點
利用
LockSupport.unpark(currentNode.next.thread)
喚醒後繼節點(接獲取的第四步)
2.3 共享模式
共享式同步狀態調用的方法是acquireShared
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
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);
}
}
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 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
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 (;;) {
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;
}
}
共享鎖流程
獲取共享鎖流程
調用acquireShared(arg)入口方法
進入tryAcquireShared(arg)模版方法獲取同步狀態,如果返返回值>=0,則說明同步狀態(state)有剩餘,獲取鎖成功直接返回
如果tryAcquireShared(arg)返回值<0,說明獲取同步狀態失敗,向隊列尾部添加一個共享類型的Node節點,隨即該節點進入自旋狀態
自旋時,首先檢查前驅節點釋放爲頭節點&tryAcquireShared()是否>=0(即成功獲取同步狀態)
如果是,則說明當前節點可執行,同時把當前節點設置爲頭節點,並且喚醒所有後繼節點.如果否,則利用
LockSupport.unpark(this)
掛起當前線程,等待被前驅節點喚醒釋放共享鎖流程
調用releaseShared(arg)模版方法釋放同步狀態
如果釋放成,則遍歷整個隊列,利用
LockSupport.unpark(nextNode.thread)
喚醒所有後繼節點
獨佔鎖和共享鎖在實現上的區別
- 獨佔鎖的同步狀態值爲1,即同一時刻只能有一個線程成功獲取同步狀態
- 共享鎖的同步狀態>1,取值由上層同步組件確定
- 獨佔鎖隊列中頭節點運行完成後釋放它的直接後繼節點
- 共享鎖隊列中頭節點運行完成後釋放它後面的所有節點
- 共享鎖中會出現多個線程(即同步隊列中的節點)同時成功獲取同步狀態的情況
2.4 中斷
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);
}
}
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.5 超時
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
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 true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
AQS小結
1、AQS(AbstranctQueueSynchronizer)核心思想是當共享資源空閒的時候,將處於隊列的頭節點線程啓動,並且佔用公共資源爲鎖定狀態.此時其他資源訪問公共資源則會被阻塞,其他線程會自旋開始等待公共資源的釋放.新加入的線程如果沒有獲取到鎖則會將新加入的線程加入到隊列的尾部.對於公共資源的狀態使用CAS方式實現修改,保證線程安全.
2、AQS有兩種資源獲取模式:
- 獨佔:同一時間只能有一個線程訪問執行
- 共享:可以多個線程同時訪問執行
3、AQS採用了模板方法的設計模式
4、AQS整體流程
-
獨佔模式,線程加入的時候嘗試獲取共享資源,若獲取失敗則包裝成Node類,並利用CAS將其放置AQS的列尾,然後開始自旋。自旋時判斷前驅節點是否是頭節點&是否成功獲取同步狀態,兩個條件都成立,則將當前線程的節點設置爲頭節點,如果不是,則利用
LockSupport.park(this)
將當前線程掛起 ,等待被前驅節點喚醒。頭節點在釋放鎖的時候會調用release方法,釋放鎖後會利用LockSupport.unpark(s.thread)
喚醒下一個節點的線程。 -
共享模式,線程加入的時候同樣會嘗試獲取共享資源(共享資源可同時使用數量>0)。若獲取失敗則包裝成Node類,利用CAS將其放置隊列尾部。然後開始自旋,自旋時判斷前驅節點是否是頭節點,如果前驅節點不是頭節點則
則利用
LockSupport.park(this)
將當前線程掛起。頭節點在釋放鎖的時候會調用release方法,釋放鎖後會利用LockSupport.unpark(s.thread)
喚醒下一個節點的線程。如果前驅節點是頭節點則一種進行自旋直到獲取到共享資源。
5、AQS中斷和超時
若前驅節點是頭節點且已經獲取了共享資源鎖則將前驅節點的後續節點設置爲null。否則拋出異常的方式來中斷次線程。超時也類似。
3. 併發工具類及線程池
-
容器類
ConcurrentHashMap : 側重於Map放入或者獲取的速度,而不在乎順序
ConcurrentSkipListMap : 在乎順序,需要對數據進行非常頻繁的修改
CopyOnWriteArrayList : 任何修改操作,如add、set、remove,都會拷貝原數組,修改後替換原來的數組,通過這種防禦性的方式,實現另類的線程安全。
CopyOnWriteArraySet
-
併發隊列的實現
BlockedQueue: ArrayBlockingQueue、SynchorousQueue
-
同步結構
CountDownLatch 允許一個或多個線程等待某些操作完成 CountDownLatch操作的是事件
CyclicBarrier 一種輔助性的同步結構,允許多個線程等待到大某個屏障 CyclicBarrier側重點是線程
Semaphore Java版本的信號量實現
Phaser 功能與CountDownLatch很接近,允許線程動態的註冊到Phaser上面,而CountDownLatch是不能動態設置的。設計初衷是實現多個線程類似步驟、階段場景的協調,線程註冊等待屏障條件出發,進而協調彼此間的行動。
-
強大的Executor框架
可以創建各種不同類型的線程池,調度任務運行等,絕大部分情況下,不再需要自己從頭實現線程池和任務調度器。
3.1 容器類
ConcurrentHashMap之前已經解析過了。
ConcurrentSkipListMap
ConcurrentSkipListMap 多線程下安全的有序鍵值對。 ConcurrentSkipListMap是通過跳錶來實現的。跳錶是一個鏈表,但是通過使用“跳躍式”查找的方式使得插入、讀取數據時複雜度變成了O(logn)。
跳錶(Skip List)是一種**“空間換時間”**的算法設計思想。我們來通過一個例子來看下Skip List 是如何實現查找的。
首先我們有一個有排按照重量大小排序的蘋果100個(假設沒有重量重複的蘋果)。
如果是普通的鏈表要找到重量爲3Kg的蘋果。如果不是調錶結構我們從第一個開始稱重,然後第二個直到找到重量是3Kg的。
如果是跳錶則會有如下結構(暫時假設層級只有2)
1kg | 2.95kg | 3,5kg | 6.7kg | ||||||
---|---|---|---|---|---|---|---|---|---|
1kg | 1.1kg | 2.95kg | … | 3Kg | 3.5kg | … | 5kg | … | 6.7kg |
即可以理解爲這100個蘋果中有幾個蘋果是明確標籤的標籤上寫着重量,但是我們要翻開標籤才能看到。那麼這個時候我們找3kg的蘋果 的時候我們就可以這樣做先翻開第一個標籤發現是1kg,那麼3kg肯定在後面。然後我們翻開第二個標籤一看發現是2.95。3kg還在後面,我們繼續翻標籤找到第一個大於3的標籤,那麼3kg肯定在這個標籤和上一個標籤之間,我們只需要測量這中間的蘋果即可找到3kg的蘋果。這樣查詢效率就大大提高了。
層數是根據一種隨機算法得到的,爲了不讓層數過大,還會有一個最大層數MAX_LEVEL限制,隨機算法生成的層數不得大於該值。
在實際程序使用中標籤肯定指會向一個下一層的一個蘋果。且標籤肯定是一個有序的,不然我們也無法快速定位了。
Skip List的基本思想
- 跳錶由很多層組成;
- 每一層都是一個有序鏈表;
- 對於每一層的任意結點,不僅有指向下一個結點的指針,也有指向其下一層的指針。
ConcurrentSkipListMap結構解析
- 主要結構
// 主要結構
private static final Object BASE_HEADER = new Object(); // 最底層鏈表的頭指針BASE_HEADER
private transient volatile HeadIndex<K, V> head; // 最上層鏈表的頭指針head
// 普通結點 Node 定義
static final class Node<K, V> {
final K key;
volatile Object value;
volatile Node<K, V> next;
// ...
}
// 頭索引結點 HeadIndex
static final class HeadIndex<K,V> extends Index<K,V> {
final int level; // 層級
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level;
}
}
// 索引結點 Index 定義
static class Index<K, V> {
final Node<K, V> node; // node指向最底層鏈表的Node結點
final Index<K, V> down; // down指向下層Index結點
volatile Index<K, V> right; // right指向右邊的Index結點
// ...
}
Node:最底層鏈表中的結點,保存着實際的鍵值對,如果單獨看底層鏈,其實就是一個按照Key有序排列的單鏈表。
HeadIndex:結點是各層鏈表的頭結點,它是Index類的子類,唯一的區別是增加了一個
level
字段,用於表示當前鏈表的級別,越往上層,level值越大。Index:結點是除底層鏈外,其餘各層鏈表中的非頭結點(見示意圖中的藍色結點)。每個Index結點包含3個指針:
down
、right
、node
。down和right指針分別指向下層結點和後繼結點,node指針指向其最底部的node結點。
- 構造方法
// 構造方法
// 空Map.
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
// 指定比較器的構造器
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
// 從給定Map構建的構造器
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.comparator = null;
initialize();
putAll(m);
}
// 從給定SortedMap構建的構造器、並且Key的順序與原來保持一致.
public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
this.comparator = m.comparator();
initialize();
buildFromSorted(m);
}
// 將一些字段置初始化null,然後將head指針指向新創建的HeadIndex結點。
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null),null, null, 1);
}
- 主要方法
put
// put
public V put(K key, V value) {
if (value == null) // ConcurrentSkipListMap的Value不能爲null
throw new NullPointerException();
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
Node<K,V> z; // added node
if (key == null) // key不能爲空
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
// 找到插入節點位置
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) {
Object v; int c;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
z = new Node<K,V>(key, value, n);
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}
// 判斷是否需要擴層
int rnd = ThreadLocalRandom.nextSecondarySeed();
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0)
++level;
Index<K,V> idx = null;
HeadIndex<K,V> h = head;
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null);
}
else { // try to grow by one level
level = max + 1; // hold in array and later pick the one to use
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])new Index<?,?>[level+1];
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
h = head;
int oldLevel = h.level;
if (level <= oldLevel) // lost race to add level
break;
HeadIndex<K,V> newh = h;
Node<K,V> oldbase = h.node;
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
if (casHead(h, newh)) {
h = newh;
idx = idxs[level = oldLevel];
break;
}
}
}
// find insertion points and splice in
splice: for (int insertionLevel = level;;) {
int j = h.level;
for (Index<K,V> q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break splice;
if (r != null) {
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
findNode(key);
break splice;
}
if (--insertionLevel == 0)
break splice;
}
if (--j >= insertionLevel && j < level)
t = t.down;
q = q.down;
r = q.right;
}
}
}
return null;
}
// 查找小於且最接近給定key的Node結點
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
Node<K,V> n = r.node;
K k = n.key;
if (n.value == null) {
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
if (cpr(cmp, key, k) > 0) { // key大於k,繼續向右查找
q = r;
r = r.right;
continue;
}
}
// 已經到了level1的層、直接返回對應的Node結點
if ((d = q.down) == null)
return q.node;
// 轉到下一層,繼續查找(level-1層)
q = d;
r = d.right;
}
}
}
remove
在刪除鍵值對時,不會立即執行刪除,而是通過引入**“標記結點”,以“懶刪除”**的方式進行,以提高併發效率。
doRemove方法首先會找到待刪除的結點,在它和後繼結點之間插入一個value爲null的標記結點(如下圖中的綠色結點),然後改變其前驅結點的指向
public V remove(Object key) {
return doRemove(key, null);
}
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) < 0)
break outer;
if (c > 0) {
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v))
break outer;
if (!n.casValue(v, null))
break;
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(key); // retry via findNode
else {
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
}
return null;
}
get
找到“小於且最接近給定key”的Node結點,然後用了三個指針:b -> n -> f,n用於定位最終查找的Key,然後順着鏈表一步步向下查
public V get(Object key) {
return doGet(key);
}
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}
CopyOnWriteArrayList
先了解CopyOnWrite機制
CopyOnWrite容器即寫時複製的容器。通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裏添加元素,添加完元素之後,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,因爲當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
get
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
add
add(E e) , add(int index , E element) ,addIfAbsent(E e) , addAllAbsent(Collection<? extends E> c) 等添加操作
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index; //要移動的元素大小
if (numMoved == 0) //尾端插入
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
//按index分前後兩部分進行拷貝
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
// 獲取當前array數組,查找是否含有e元素,沒有則調用addIfAbsent(e, snapshot)插入
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock(); // 鎖
try {
Object[] current = getArray();//獲取最新的array對象
int len = current.length;
if (snapshot != current) { //若是如上所說,在此期間array發生更改
// 檢查新數組中是否含有e元素
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
//在current上進行操作
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
set
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
remove
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1; //要移動的元素個數
if (numMoved == 0) // 刪除的是末尾元素
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
/** array數組的快照 */
private final Object[] snapshot;
/** 指針 */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
}
3.2 併發隊列
ConcurrentLinkedQueue
一個基於鏈接節點的無界線程安全FIFO(先進先出)隊列。隊列獲取操作從隊列頭部獲得元素。當多個線程共享訪問一個公共 collection 時,ConcurrentLinkedQueue 是一個恰當的選擇。此隊列不允許使用 null 元素。
下面內容轉自 https://www.jianshu.com/p/ce6108e4b2c4
--------------------- 分割線 -------------------------
一個空的ConcurrentLinkedQueue包含一個空節點(數據字段=null),隊列的HEAD和TAIL指針都指向這個節點。當經過一些出入隊列的操作以後,隊列的結構可能類似下面
node1 -> node2 -> node3 -> node4 -> node5
HEAD tail
node1 -> node2 -> node3 -> node4 -> node5
head tail
此結構表示node1已經出列但是還保持這個next的指針指向了node2。隊尾的情況是表示發生了併發
node1 <- | node2 -> node3 -> node4 -> node5
head tail
node1的next節點指向自己,這表示node1已經斷開了和隊列的聯繫,如果將node1的next設置爲null則無法判斷node1是否是尾節點。
上面這些情形是ConcurrentLinkedQueue在執行一些操作之後可能處於的狀態,之所於允許這些看起來不夠嚴謹的狀態,是爲了在併發過程中提高效率。但是不管如何,在整個生命週期內,算法保持以下屬性:
- 鏈表裏面至少會有一個節點,數據字段爲null的節點是空節點;
- 順着HEAD指針肯定能找到的真正的頭節點,並能訪問到所有節點;
- TAIL指針不一定指向有效節點,更不能保證指向真正的尾節點,但是它大部分情況下指向尾節點或接近尾節點,因此可以提高效率;
- 和隊列斷開的節點,next字段指向自身;
- 對於入了隊列的節點(哪怕又出了隊列),只有尾節點的next字段纔等於null。
--------------------- 分割線 -------------------------
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentLinkedQueue.class;
headOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("head"));
tailOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("tail"));
} catch (Exception e) {
throw new Error(e);
}
}
// 構造
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
-
add進棧
public boolean add(E e) { return offer(e); } public boolean offer(E e) { checkNotNull(e); final Node<E> newNode = new Node<E>(e); for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; if (q == null) { // p is last node if (p.casNext(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this queue, // and for newNode to become "live". if (p != t) // hop two nodes at a time casTail(t, newNode); // Failure is OK. return true; } // Lost CAS race to another thread; re-read next } // next節點指向自身表示該節點已經出棧 else if (p == q) // We have fallen off list. If tail is unchanged, it // will also be off-list, in which case we need to // jump to head, from which all live nodes are always // reachable. Else the new tail is a better bet. p = (t != (t = tail)) ? t : head; else // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } } public boolean addAll(Collection<? extends E> c) { if (c == this) // As historically specified in AbstractQueue#addAll throw new IllegalArgumentException(); // Copy c into a private chain of Nodes Node<E> beginningOfTheEnd = null, last = null; for (E e : c) { checkNotNull(e); Node<E> newNode = new Node<E>(e); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { last.lazySetNext(newNode); last = newNode; } } if (beginningOfTheEnd == null) return false; // Atomically append the chain at the tail of this collection for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; if (q == null) { // p is last node if (p.casNext(null, beginningOfTheEnd)) { // Successful CAS is the linearization point // for all elements to be added to this queue. if (!casTail(t, last)) { // Try a little harder to update tail, // since we may be adding many elements. t = tail; if (last.next == null) casTail(t, last); } return true; } // Lost CAS race to another thread; re-read next } else if (p == q) // We have fallen off list. If tail is unchanged, it // will also be off-list, in which case we need to // jump to head, from which all live nodes are always // reachable. Else the new tail is a better bet. p = (t != (t = tail)) ? t : head; else // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } }
-
poll出棧
public E poll() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; if (item != null && p.casItem(item, null)) { // Successful CAS is the linearization point // for item to be removed from this queue. if (p != h) // hop two nodes at a time updateHead(h, ((q = p.next) != null) ? q : p); return item; } else if ((q = p.next) == null) { updateHead(h, p); return null; } else if (p == q) continue restartFromHead; else p = q; } } }
-
remove
public boolean remove(Object o) { if (o != null) { Node<E> next, pred = null; for (Node<E> p = first(); p != null; pred = p, p = next) { boolean removed = false; E item = p.item; if (item != null) { if (!o.equals(item)) { next = succ(p); continue; } removed = p.casItem(item, null); } next = succ(p); if (pred != null && next != null) // unlink pred.casNext(p, next); if (removed) return true; } } return false; } // next指向自己 final Node<E> succ(Node<E> p) { Node<E> next = p.next; return (p == next) ? head : next; }
-
迭代器
private class Itr implements Iterator<E> { /** * Next node to return item for. */ private Node<E> nextNode; /** * nextItem holds on to item fields because once we claim * that an element exists in hasNext(), we must return it in * the following next() call even if it was in the process of * being removed when hasNext() was called. */ private E nextItem; /** * Node of the last returned item, to support remove. */ private Node<E> lastRet; Itr() { advance(); } /** * Moves to next valid node and returns item to return for * next(), or null if no such. */ private E advance() { lastRet = nextNode; E x = nextItem; Node<E> pred, p; if (nextNode == null) { p = first(); pred = null; } else { pred = nextNode; p = succ(nextNode); } for (;;) { if (p == null) { nextNode = null; nextItem = null; return x; } E item = p.item; if (item != null) { nextNode = p; nextItem = item; return x; } else { // skip over nulls Node<E> next = succ(p); if (pred != null && next != null) pred.casNext(p, next); p = next; } } } public boolean hasNext() { return nextNode != null; } public E next() { if (nextNode == null) throw new NoSuchElementException(); return advance(); } public void remove() { Node<E> l = lastRet; if (l == null) throw new IllegalStateException(); // rely on a future traversal to relink. l.item = null; lastRet = null; } }
BlockingQueue
阻塞隊列。一個接口繼承Queue。實現類
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque,SynchronousQueue。
ArrayBlockingQueue
有界隊列,從構造方法和屬性中可以看出來
final Object[] items;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
增、刪
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
源碼分析就不寫很多了,有興趣的可以自己研究下或者取網上找有很多。這裏直接給結論:
創建ArrayBlockingQueue時指定隊列大小(有的人也叫做緩存區),因爲使用的是Object數組(環形數組)實現因此
建之後就無法改變隊列大小。在入隊的時候先使用ReentrantLock.lock()
獲取鎖,然後在在進行入隊操作,
一旦隊列已滿則入隊失敗,否則正常入隊。因此可以看出來ArrayBlockingQueue是一個線程安全的阻塞隊列。
入隊時會修改隊列的count
putIndex
屬性,然後釋放鎖。出隊的時候也同樣先獲取鎖。
然後直接對takeIndex
位置的元素進行出隊操作,改變count
takeIndex
。刪除操作就相對麻煩一些,
需要判斷刪除的節點是否時頭節點,若是則很簡單直接將頭節點設置爲null,然後減少count
使用迭代器
變更隊列中的數據順序,若不是頭節點則需要將被刪除節點後的所有節點左移。
API方法:
boolean |
add(E e) 將指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量),在成功時返回 true ,如果此隊列已滿,則拋出 IllegalStateException 。 |
---|---|
void |
clear() 自動移除此隊列中的所有元素。 |
boolean |
contains(Object o) 如果此隊列包含指定的元素,則返回 true 。 |
int |
drainTo(Collection<? super E> c) 移除此隊列中所有可用的元素,並將它們添加到給定 collection 中。 |
int |
drainTo(Collection<? super E> c, int maxElements) 最多從此隊列中移除給定數量的可用元素,並將這些元素添加到給定 collection 中。 |
Iterator<E> |
iterator() 返回在此隊列中的元素上按適當順序進行迭代的迭代器。 |
boolean |
offer(E e) 將指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量),在成功時返回 true ,如果此隊列已滿,則返回 false 。 |
boolean |
offer(E e, long timeout, TimeUnit unit) 將指定的元素插入此隊列的尾部,如果該隊列已滿,則在到達指定的等待時間之前等待可用的空間。 |
E |
peek() 獲取但不移除此隊列的頭;如果此隊列爲空,則返回 null 。 |
E |
poll() 獲取並移除此隊列的頭,如果此隊列爲空,則返回 null 。 |
E |
poll(long timeout, TimeUnit unit) 獲取並移除此隊列的頭部,在指定的等待時間前等待可用的元素(如果有必要)。 |
void |
put(E e) 將指定的元素插入此隊列的尾部,如果該隊列已滿,則等待可用的空間。 |
int |
remainingCapacity() 返回在無阻塞的理想情況下(不存在內存或資源約束)此隊列能接受的其他元素數量。 |
boolean |
remove(Object o) 從此隊列中移除指定元素的單個實例(如果存在)。 |
int |
size() 返回此隊列中元素的數量。 |
E |
take() 獲取並移除此隊列的頭部,在元素變得可用之前一直等待(如果有必要)。 |
Object[] |
toArray() 返回一個按適當順序包含此隊列中所有元素的數組。 |
<T> T[] |
toArray(T[] a) 返回一個按適當順序包含此隊列中所有元素的數組;返回數組的運行時類型是指定數組的運行時類型。 |
String |
toString() 返回此 collection 的字符串表示形式。 |
DelayQueue
private final transient ReentrantLock lock = new ReentrantLock();
// 優先隊列,用於存儲元素,並按優先級排序
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 用於優化內部阻塞通知的線程
private Thread leader = null;
// 用於實現阻塞的Condition對象
private final Condition available = lock.newCondition();
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
// 獲取全局獨佔鎖
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
// 獲取隊首元素
E first = q.peek();
// 隊首爲空,則阻塞當前線程
if (first == null)
available.await();
else {
// 獲取隊首元素的超時時間
long delay = first.getDelay(NANOSECONDS);
// 已超時,直接出隊
if (delay <= 0)
return q.poll();
// 釋放first的引用,避免內存泄漏
first = null; // don't retain ref while waiting
// leader != null表明有其他線程在操作,阻塞當前線程
if (leader != null)
available.await();
else {
// leader指向當前線程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 超時阻塞
available.awaitNanos(delay);
} finally {
// 釋放leader
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// leader爲null並且隊列不爲空,說明沒有其他線程在等待,那就通知條件隊列
if (leader == null && q.peek() != null)
available.signal();
// 釋放全局獨佔鎖
lock.unlock();
}
}
LinkedBlockingQueue
LinkedBlockingQueue是一個無界緩存等待隊列。當前執行的線程數量達到corePoolSize的數量時,剩餘的元素會在阻塞隊列裏等待。(所以在使用此阻塞隊列時maximumPoolSizes就相當於無效了),每個線程完全獨立於其他線程。生產者和消費者使用獨立的鎖來控制數據的同步,即在高併發的情況下可以並行操作隊列中的數據。
private final int capacity;
private final AtomicInteger count = new AtomicInteger();
transient Node<E> head;
private transient Node<E> last;
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
從上面的屬性我們知道,每個添加到LinkedBlockingQueue隊列中的數據都將被封裝成Node節點,添加的鏈表隊列中,其中head和last分別指向隊列的頭結點和尾結點。與ArrayBlockingQueue不同的是,LinkedBlockingQueue內部分別使用了takeLock 和 putLock 對併發進行控制,也就是說,添加和刪除操作並不是互斥操作,可以同時進行,這樣也就可以大大提高吞吐量。如果不指定隊列的容量大小,也就是使用默認的Integer.MAX_VALUE,如果存在添加速度大於刪除速度時候,有可能會內存溢出,這點在使用前希望慎重考慮。另外,LinkedBlockingQueue對每一個lock鎖都提供了一個Condition用來掛起和喚醒其他線程。
源碼閱讀略
API
boolean |
add(E e) 在不違反容量限制的情況下,將指定的元素插入此雙端隊列的末尾。 |
---|---|
void |
addFirst(E e) 如果立即可行且不違反容量限制,則將指定的元素插入此雙端隊列的開頭;如果當前沒有空間可用,則拋出 IllegalStateException 。 |
void |
addLast(E e) 如果立即可行且不違反容量限制,則將指定的元素插入此雙端隊列的末尾;如果當前沒有空間可用,則拋出 IllegalStateException 。 |
void |
clear() 以原子方式 (atomically) 從此雙端隊列移除所有元素。 |
boolean |
contains(Object o) 如果此雙端隊列包含指定的元素,則返回 true 。 |
Iterator<E> |
descendingIterator() 返回在此雙端隊列的元素上以逆向連續順序進行迭代的迭代器。 |
int |
drainTo(Collection<? super E> c) 移除此隊列中所有可用的元素,並將它們添加到給定 collection 中。 |
int |
drainTo(Collection<? super E> c, int maxElements) 最多從此隊列中移除給定數量的可用元素,並將這些元素添加到給定 collection 中。 |
E |
element() 獲取但不移除此雙端隊列表示的隊列的頭部。 |
E |
getFirst() 獲取,但不移除此雙端隊列的第一個元素。 |
E |
getLast() 獲取,但不移除此雙端隊列的最後一個元素。 |
Iterator<E> |
iterator() 返回在此雙端隊列元素上以恰當順序進行迭代的迭代器。 |
boolean |
offer(E e) 如果立即可行且不違反容量限制,則將指定的元素插入此雙端隊列表示的隊列中(即此雙端隊列的尾部),並在成功時返回 true ;如果當前沒有空間可用,則返回 false 。 |
boolean |
offer(E e, long timeout, TimeUnit unit) 將指定的元素插入此雙端隊列表示的隊列中(即此雙端隊列的尾部),必要時將在指定的等待時間內一直等待可用空間。 |
boolean |
offerFirst(E e) 如果立即可行且不違反容量限制,則將指定的元素插入此雙端隊列的開頭,並在成功時返回 true ;如果當前沒有空間可用,則返回 false 。 |
boolean |
offerFirst(E e, long timeout, TimeUnit unit) 將指定的元素插入此雙端隊列的開頭,必要時將在指定的等待時間內等待可用空間。 |
boolean |
offerLast(E e) 如果立即可行且不違反容量限制,則將指定的元素插入此雙端隊列的末尾,並在成功時返回 true ;如果當前沒有空間可用,則返回 false 。 |
boolean |
offerLast(E e, long timeout, TimeUnit unit) 將指定的元素插入此雙端隊列的末尾,必要時將在指定的等待時間內等待可用空間。 |
E |
peek() 獲取但不移除此雙端隊列表示的隊列的頭部(即此雙端隊列的第一個元素);如果此雙端隊列爲空,則返回 null 。 |
E |
poll() 獲取並移除此雙端隊列表示的隊列的頭部(即此雙端隊列的第一個元素);如果此雙端隊列爲空,則返回 null 。 |
E |
poll(long timeout, TimeUnit unit) 獲取並移除此雙端隊列表示的隊列的頭部(即此雙端隊列的第一個元素),如有必要將在指定的等待時間內等待可用元素。 |
E |
pollLast() 獲取並移除此雙端隊列的最後一個元素;如果此雙端隊列爲空,則返回 null 。 |
E |
pollLast(long timeout, TimeUnit unit) 獲取並移除此雙端隊列的最後一個元素,必要時將在指定的等待時間內等待可用元素。 |
E |
pop() 從此雙端隊列所表示的堆棧中彈出一個元素。 |
void |
push(E e) 將元素推入此雙端隊列表示的棧。 |
void |
put(E e) 將指定的元素插入此雙端隊列表示的隊列中(即此雙端隊列的尾部),必要時將一直等待可用空間。 |
int |
remainingCapacity() 返回理想情況下(沒有內存和資源約束)此雙端隊列可不受阻塞地接受的額外元素數。 |
E |
remove() 獲取並移除此雙端隊列表示的隊列的頭部。 |
boolean |
remove(Object o) 從此雙端隊列移除第一次出現的指定元素。 |
E |
removeFirst() 獲取並移除此雙端隊列第一個元素。 |
boolean |
removeFirstOccurrence(Object o) 從此雙端隊列移除第一次出現的指定元素。 |
E |
removeLast() 獲取並移除此雙端隊列的最後一個元素。 |
boolean |
removeLastOccurrence(Object o) 從此雙端隊列移除最後一次出現的指定元素。 |
int |
size() 返回此雙端隊列中的元素數。 |
E |
take() 獲取並移除此雙端隊列表示的隊列的頭部(即此雙端隊列的第一個元素),必要時將一直等待可用元素。 |
E |
takeFirst() 獲取並移除此雙端隊列的第一個元素,必要時將一直等待可用元素。 |
E |
takeLast() 獲取並移除此雙端隊列的最後一個元素,必要時將一直等待可用元素。 |
Object[] |
toArray() 返回以恰當順序(從第一個元素到最後一個元素)包含此雙端隊列所有元素的數組。 |
<T> T[] |
toArray(T[] a) 返回以恰當順序包含此雙端隊列所有元素的數組;返回數組的運行時類型是指定數組的運行時類型。 |
String |
toString() 返回此 collection 的字符串表示形式。 |
SynchronousQueue
參考資料:https://www.cnblogs.com/dwlsxj/p/Thread.html
不像ArrayBlockingQueue或LinkedListBlockingQueue,SynchronousQueue內部並沒有數據緩存空間,你不能調用peek()方法來看隊列中是否有數據元素,因爲數據元素只有當你試着取走的時候纔可能存在,不取走而只想偷窺一下是不行的,當然遍歷這個隊列的操作也是不允許的。隊列頭元素是第一個排隊要插入數據的線程,而不是要交換的數據。數據是在配對的生產者和消費者線程之間直接傳遞的,並不會將數據緩衝數據到隊列中。可以這樣來理解:生產者和消費者互相等待對方,握手,然後一起離開。
1、不能在同步隊列上進行 peek,因爲僅在試圖要取得元素時,該元素才存在;
2、除非另一個線程試圖移除某個元素,否則也不能(使用任何方法)添加元素;也不能迭代隊列,因爲其中沒有元素可用於迭代。隊列的頭是嘗試添加到隊列中的首個已排隊線程元素; 如果沒有已排隊線程,則不添加元素並且頭爲 null。
3、對於其他 Collection 方法(例如 contains),SynchronousQueue 作爲一個空集合。此隊列不允許 null 元素。
4、它非常適合於傳遞性設計,在這種設計中,在一個線程中運行的對象要將某些信息、事件或任務傳遞給在另一個線程中運行的對象,它就必須與該對象同步。
5、對於正在等待的生產者和使用者線程而言,此類支持可選的公平排序策略。默認情況下不保證這種排序。 但是,使用公平設置爲 true 所構造的隊列可保證線程以 FIFO 的順序進行訪問。 公平通常會降低吞吐量,但是可以減小可變性並避免得不到服務。
6、SynchronousQueue的以下方法:* iterator() 永遠返回空,因爲裏面沒東西。 * peek() 永遠返回null。 * put() 往queue放進去一個element以後就一直wait直到有其他thread進來把這個element取走。 * offer() 往queue裏放一個element後立即返回,如果碰巧這個element被另一個thread取走了,offer方法返回true,認爲offer成功;否則返回false。 * offer(2000, TimeUnit.SECONDS) 往queue裏放一個element但是等待指定的時間後才返回,返回的邏輯和offer()方法一樣。 * take() 取出並且remove掉queue裏的element(認爲是在queue裏的。。。),取不到東西他會一直等。 * poll() 取出並且remove掉queue裏的element(認爲是在queue裏的。。。),只有到碰巧另外一個線程正在往queue裏offer數據或者put數據的時候,該方法纔會取到東西。否則立即返回null。 * poll(2000, TimeUnit.SECONDS) 等待指定的時間然後取出並且remove掉queue裏的element,其實就是再等其他的thread來往裏塞。 * isEmpty()永遠是true。 * remainingCapacity() 永遠是0。 * remove()和removeAll() 永遠是false。
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
static class WaitQueue implements java.io.Serializable { }
static class LifoWaitQueue extends WaitQueue {
private static final long serialVersionUID = -3633113410248163686L;
}
static class FifoWaitQueue extends WaitQueue {
private static final long serialVersionUID = -3623113410248163686L;
}
private ReentrantLock qlock;
private WaitQueue waitingProducers;
private WaitQueue waitingConsumers;
private transient volatile Transferer<E> transferer;
TransferQueue持有節點類QNode(持有next節點的引用),TransferQueue本身還有head和tai屬性。其方法是使用CAS是實現的。
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
if (transferer.transfer(e, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // constructed/reused as needed
// put操作這裏e不爲null,isData爲true
boolean isData = (e != null);
// 自旋
for (;;) {
QNode t = tail;
QNode h = head;
// 還沒有初始化,則continue重新來過
if (t == null || h == null) // saw uninitialized value
continue; // spin
// h == t則說明還沒有存入元素。這是爲true,進入。進入這個if就會堵塞元素,不管take還是put。
if (h == t || t.isData == isData) { // empty or same-mode
// tn爲null。(順便說一句:next變量是volatile修飾,多線程可見,所以下面這個複製操作是線程安全的。)
QNode tn = t.next;
// 其他線程把尾節點改變了,則再旋一次
if (t != tail) // inconsistent read
continue;
// tn爲null
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
// 無超時機制
if (timed && nanos <= 0) // can't wait
return null;
// 創建put操作存放數據的新節點,isData爲true。
// 如果沒有元素,take操作進入這裏,也創建了一個新node,也就是說take操作堵塞的時候,也會創建節點,
if (s == null)
s = new QNode(e, isData);
// cas替換,從:head(tail)(dummy)到head(tail)(dumy)->S
// cas替換,尾節點的next指向新建立的s節點。失敗則再旋一次
if (!t.casNext(null, s)) // failed to link in
continue;
/*cas替換,把TransferQuene的舊尾節點t替換爲新節點s。
至此,變成了head(dumy)=>s(tail)
*/
advanceTail(t, s); // swing tail and wait
// 下面這個方法調用就是具體的堵塞調用,看下一段代碼分析
Object x = awaitFulfill(s, e, timed, nanos);
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
if (!s.isOffList()) { // not already unlinked
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
return (x != null) ? (E)x : e;
} else { // complementary-mode
QNode m = h.next; // node to fulfill
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
advanceHead(h, m); // successfully fulfilled
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
// s是新節點,e是put的真正數據,timed是是否超時,nanos是超時時間
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
// deadLine爲0
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
// 自旋次數,第一次put的時候,鏈表結構爲:head(dumy)=>s(tail),所以
// head.hext == s,timed爲不超時false,所以spin=maxUntimedSpins=512,
// 如果第二個put堵塞,則結構爲:head(dumy)=>s=>s1(tail),而head.next和s1(新put的元素是s1)不相等,所以,spin=0直接堵塞。
//(爲什麼會自旋,我估計是爲了,高併發下,take操作
// 和put操作很可能再極短時間進行,這樣的話,就不需要喚醒線程和堵塞線程)
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 當前線程準備中斷,則s節點的item指向s節點自己
if (w.isInterrupted())
s.tryCancel(e);
Object x = s.item;
// 這裏爲false,只有當其他線程把這個元素替換了,比如put堵塞在這
// 裏的時候,take就會把這個元素替換掉,然後put喚醒的時候就能直接return了。
if (x != e)
return x;
if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
s.tryCancel(e);
continue;
}
}
// 這裏spins初始爲512,一直遞減到0
if (spins > 0)
--spins;
else if (s.waiter == null)
// node節點和waiter和當前線程關聯上,爲了公平的喚醒。
s.waiter = w;
else if (!timed)
// 鎖住當前線程。設置thread的block變量爲parkBlocker指向的對象transferQueue。
LockSupport.park(this);
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
public E take() throws InterruptedException {
E e = transferer.transfer(null, false, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // constructed/reused as needed
// isData爲false
boolean isData = (e != null);
for (;;) {
QNode t = tail;
QNode h = head;
if (t == null || h == null) // saw uninitialized value
continue; // spin
// 第一put後結構變成了:head(dumy)=>s(tail)
/* 所以這裏h不等於t,t.isData爲true,
*所以這裏不成立,走else塊。只要不堵塞,都會走else塊。put操作如
果不堵塞,也會走else塊。
*/
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
if (t != tail) // inconsistent read
continue;
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
if (timed && nanos <= 0) // can't wait
return null;
if (s == null)
s = new QNode(e, isData);
if (!t.casNext(null, s)) // failed to link in
continue;
advanceTail(t, s); // swing tail and wait
Object x = awaitFulfill(s, e, timed, nanos);
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
if (!s.isOffList()) { // not already unlinked
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
return (x != null) ? (E)x : e;
} else { // complementary-mode
//head(dumy)=>s(tail)
// m就是之前put的哪個節點
QNode m = h.next; // node to fulfill
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
/*
isData=(e!=null),這是take操作,所以e爲null,所以isData爲false,x爲之前put進去的值,爲非null
x == m說明已經取消了,之前put操作的時候,
*awaitFulfill方法裏,如果當前線程準備中斷,
*就會調用qnode的tryCancel方法,讓qnode的next指向自己,代表
* 這個節點取消了。
head(dumy)=>s(tail)
*!m.casItem(x, e):直接替換item的值,
這樣,在take方法堵塞在awaitFulfill方法裏的時候,
這裏直接把之前take方法創建的node的item改掉,
然後喚醒take的線程,然後take操作獲取到這個新值了和它之前的值不一樣,則直接跳出循環,不堵塞。
*/
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
// 變成了head(s)(tail)
advanceHead(h, m); // successfully fulfilled
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
SynchronousQueue還有offer方法,poll方法。add方法。
offer方法,不會堵塞,可以存進去,則存進去(poll或take操作正在堵塞等着獲取元素),否則,直接返回false。
poll方法可以有超時時間,和take差不多,take沒有超時時間。
add方法,調用的就是offer方法,不過add方法,添加不進去,則直接報錯。
BlockingQueue小結
對ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue進行下小結
ArrayBlockingQueue:
基於數組實現
final Object[] items
,因爲是基於數組實現所以是有界隊列,在初始化的時候就確定了大小。數組是一個“環形”的數組,頭尾相接。在入隊操作中使用了ReentrantLock.lock()
獲取鎖,然後在在進行入隊操作,一旦隊列已滿則入隊失敗,否則正常入隊。出隊也是一樣通過ReentrantLock.lock()
獲取鎖。內部使用了倆個屬性takeIndex
putIndex
分別對應數組入隊和出隊的時候所在的數組位置count
爲隊列元素數量。提供了入隊、等待時間內入隊、出隊、等待時間內出隊、查看隊列頭部元素等操作。LinkedBlockingQueue:
基於鏈表的阻塞隊列,容量是無限的,當然在構造實例的時候可以設置上限。本身持有
head
last
節點表示鏈表的頭元素和尾元素。提供了倆個鎖putLock
takeLock
這意味着LinkedBlockingQueue的入隊和出隊操作可以並行進行。入隊的時候獲取putLock
然後鎖定入隊操作,進行入隊操作。出隊的時候同樣獲取出隊鎖takeLock
。注意的是其他操作都會獲取兩個鎖然後在進行相關操作。SynchronousQueue:
沒有數據緩存空間(沒有數組屬性或者鏈表結構)。因此無法支持查看隊列頭部信息的方法。任何一個對SynchronousQueue寫需要等到一個對SynchronousQueue的讀操作,反之亦然。一個讀操作需要等待一個寫操作,相當於是交換通道,提供者和消費者是需要組隊完成工作,缺少一個將會阻塞線程,直到等到配對爲止。分爲公平模式和非公平模式。公平模式即是先進先出的方式。內部類
TransferQueue WaitQueue
。take和put操作都是使用內部類實現的。如何實現公平鎖和非公平鎖可以查看資料。
3.3 同步結構
countDownLatch
countDownLatch這個類使一個線程等待其他線程各自執行完畢後再執行。
// 構造器可指定容器含量
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 主要方法
//調用await()方法的線程會被掛起,它會等待直到count值爲0才繼續執行
public void await() throws InterruptedException { };
//和await()類似,只不過等待一定的時間後count值還沒變爲0的話就會繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//將count值減1
public void countDown() { };
初始化的時候傳入正數然後計數器按照這個正數進行計數。內部類Sync
繼承AQS
重寫了tryAcquireShared
和tryReleaseShared
方法,這說明countDownLatch使用的共享鎖。
內部類Sync同樣繼承AQS;
AQS的state代表count;
初始化使用計數器count;
count代表多個線程執行或者某個操作執行次數;
countDown()方法將會將count-1;
count爲0將會釋放所有等待線程;
await方法將會阻塞直到count爲0;
CountDownLatch是一次性的,計數器的值只能在構造方法中初始化一次,之後沒有任何機制再次對其設置值,當CountDownLatch使用完畢後,它不能再次被使用。
count不爲0,但是等待時間過去將會返回false。
開關鎖應用;
問題分解應用–並行性;
CyclicBarrier
參考資料:https://www.cnblogs.com/liuyun1995/p/8529360.html
// 屬性及構造
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
//同步操作鎖
private final ReentrantLock lock = new ReentrantLock();
//線程攔截器
private final Condition trip = lock.newCondition();
//每次攔截的線程數
private final int parties;
//換代前執行的任務
private final Runnable barrierCommand;
//表示柵欄的當前代
private Generation generation = new Generation();
//計數器
private int count;
//靜態內部類Generation
private static class Generation {
boolean broken = false;
}
CyclicBarrier並沒有直接使用AQS來定義自己的Sync同步輔助類。但是其使用的ReentrantLock來作爲鎖使用。CyclicBarrier是一個同步輔助類,它允許一組線程相互等待直到所有線程都到達一個公共的屏障點。CyclicBarrier在涉及一定大小的線程的程序而這些線程有時必須彼此等待的情況下很有用。
3.14 Executor框架
- Executor:一個接口,其定義了一個接收Runnable對象的方法executor,其方法簽名爲executor(Runnable command),
- ExecutorService:是一個比Executor使用更廣泛的子類接口,其提供了生命週期管理的方法,以及可跟蹤一個或多個異步任務執行狀況返回Future的方法
- AbstractExecutorService:ExecutorService執行方法的默認實現
- ScheduledExecutorService:一個可定時調度任務的接口
- ScheduledThreadPoolExecutor:ScheduledExecutorService的實現,一個可定時調度任務的線程池
- ThreadPoolExecutor:線程池,可以通過調用Executors以下靜態工廠方法來創建線程池並返回一個ExecutorService對象。
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
解釋下構造中參數的含義:
-
corePoolSize:核心線程數,如果運行的線程少於corePoolSize,則創建新線程來執行新任務,即使線程池中的其他線程是空閒的
-
maximumPoolSize:最大線程數,可允許創建的線程數,corePoolSize和maximumPoolSize設置的邊界自動調整池大小
-
keepAliveTime:如果線程數多於corePoolSize,則這些多餘的線程的空閒時間超過keepAliveTime時將被終止
-
unit:keepAliveTime參數的時間單位
-
workQueue:保存任務的阻塞隊列,與線程池的大小有關:
當運行的線程數少於corePoolSize時,在有新任務時直接創建新線程來執行任務而無需再進隊列
當運行的線程數等於或多於corePoolSize,在有新任務添加時則選加入隊列,不直接創建線程
當隊列滿時,在有新任務時就創建新線程
-
threadFactory:使用ThreadFactory創建新線程,默認使用defaultThreadFactory創建線程
-
handler:定義處理被拒絕任務的策略,默認使用ThreadPoolExecutor.AbortPolicy,任務被拒絕時將拋出RejectExecutorException
ThreadPoolExecutor有一個核心線程池和一個最大線程池,核心線程池內的線程不會因爲取不到任務而銷燬。
提交任務時會先嚐試添加核心線程,如果核心線程池沒滿就添加新的核心線程。不管創建過的核心線程有沒有空閒,只要來了任務都會創建新的核心線程,直到核心線程數達到最大值。當核心線程達到最大值時,再提交任務就會將任務提交到阻塞隊列中,阻塞隊列有多種實現,java的Executors工具類提供了三種ThreadPoolExecutor的構造:使用LinkedBlockingQueue,核心線程和最大線程數一樣的FixedThreadPool;使用LinkedBlockingQueue,核心線程和最大線程數都是1的SingleThreadExecutor;使用SynchronousQueue,核心線程是0、最大線程數都是integer最大值的CachedThreadPool,當阻塞隊列已滿,不能存放新的任務時,就會在最大線程池內創建會銷燬的普通線程。不管是核心線程還是普通線程都會在創建後先執行創建時放入的任務,執行完後再從阻塞隊列獲取任務。當最大線程池也達到最大值時,會觸發拒絕策略,拒絕策略可自定義,java提供的有:什麼都不做、直接拋異常、阻塞隊列拋棄第一個任務後將新任務再提交、由提交任務線程直接執行。
屬性
// 存放了worker總數
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// 阻塞隊列,存放任務
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet<Worker>();
// hashset存放worker
private final Condition termination = mainLock.newCondition();
// 當前線程池達到過的最大worker數
private int largestPoolSize;
// 線程池處理過的任務總數,只有在worker中斷的時候纔會將該worker處理的任務總數加入completedTaskCount
private long completedTaskCount;
// 用於產生worker的工廠類
private volatile ThreadFactory threadFactory;
// 拒絕策略,在線程池處理不了任務時調用
private volatile RejectedExecutionHandler handler;
// worker的最大空閒時間,超過這個時間沒取到任務會銷燬worker
private volatile long keepAliveTime;
// 是否允許核心線程超時,默認爲false,可以調用allowCoreThreadTimeOut修改值
private volatile boolean allowCoreThreadTimeOut;
// 最大核心線程數
private volatile int corePoolSize;
// 最大線程數,corePoolSize計算在內
private volatile int maximumPoolSize;
// 默認的拒絕策略,拋出異常
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread");
// boolean值,用於只中斷一個worker
private static final boolean ONLY_ONE = true;
對外暴漏方法
Executor接口就一個方法executor,入參時一個Runnable的實現類,用於提交任務。ThreadPoolExecutor重寫了executor方法。此外,ThreadPoolExecutor的繼承了AbstractExecutorService類,AbstractExecutorService提供了可提交Callable任務的submit方法,submit將任務包裝成一個FutureTask返回,調用方可根據返回值獲取執行結果。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果工作線程數小於核心線程數最大值,則創建新的核心線程處理本次任務
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//核心線程數已滿,則將任務加入阻塞隊列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//再次檢查線程池運行狀態,如果已經停止運行則將任務刪除,並執行拒絕策略
if (! isRunning(recheck) && remove(command))
reject(command);
//如果線程數爲0,則補充工作線程,添加的新工作線程不會處理本次任務,會直接從阻塞隊列取
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果阻塞隊列滿了,則添加新的工作線程處理本次任務
else if (!addWorker(command, false))
//工作線程達到最大值則執行拒絕策略
reject(command);
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
工作線程worker源碼
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
//線程對象,構造worker時將thread賦值爲自己
//啓動worker,就是通過這個thread調用自己的run方法
final Thread thread;
//第一個任務,也就是創建該工作線程時外部提交的任務
Runnable firstTask;
//worker處理完成的任務數
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//thread對象創建時,可以放入一個runnable,此處放的就是worker自己
this.thread = getThreadFactory().newThread(this);
}
public void run() {
//worker處理任務的核心代碼
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
Executors
一個工具類來獲取線程池的。
https://blog.csdn.net/fenglllle/article/details/83478382
package com.prc.threadDemo1.threadPoolPrc;
import java.util.concurrent.*;
/**
* 線程池使用練習
*/
public class ThreadPoolPrc {
public static void main(String[] args) {
// 驗證是否是單例
ThreadPoolExecutor t1 = ThreadPoolUtil.getThreadPool();
ThreadPoolExecutor t2 = ThreadPoolUtil.getThreadPool();
System.out.println(t1.equals(t2));
ThreadPoolUtil.execute(() ->{
System.out.println("1");
});
ThreadPoolPrc tp1 = new ThreadPoolPrc();
tp1.ExecutorProduce();
}
// Executors 獲取線程池
public void ExecutorProduce(){
// 沒有額外線程,只存在覈心線程,而且核心線程沒有超時機制,而且任務隊列沒有長度的限制
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i =1; i<=20; i++){
final int index=i ;
pool.execute(new Runnable(){
@Override
public void run() {
try {
System.out.println("第" +index + "個線程" +Thread.currentThread().getName());
Thread.sleep(1000);
} catch(InterruptedException e ) {
e .printStackTrace();
}
}
});
}
// 線程池關閉
pool.shutdown();
// ScheduledThreadPool
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println("延遲1秒後每三秒執行一次");
}
},1,3,TimeUnit.SECONDS);
scheduledThreadPool.shutdown();
}
}
package com.prc.threadDemo1.threadPoolPrc;
import java.util.concurrent.*;
/**
* 自定義線程池工具類
*/
public class ThreadPoolUtil {
/* 私有化構造方法 */
private ThreadPoolUtil() {super();}
//**************** 線程池參數 ***************************//
// 核心線程池數
private final static Integer COREPOOLSIZE = 2;
// 最大線程數數量
private final static Integer MAXIMUMPOOLSIZE = 4;
// 線程存活時間
private final static Integer KEEPALIVETIME = 60;
// 時間單位秒
private final static TimeUnit unit = TimeUnit.SECONDS;
// 是否允許核心線程超時,默認爲false
private final static boolean ALLOWCORETHREADTIMEOUT = false;
// 線程等待隊列
private static BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);
// 驗證是否單例使用
public static ThreadPoolExecutor getThreadPool() {
return threadPool;
}
// 線程池對象,使用static保證調用的是同一個線程池實例
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE,
KEEPALIVETIME, unit, queue, new ThreadPoolExecutor.AbortPolicy());
static {
threadPool.allowCoreThreadTimeOut(ALLOWCORETHREADTIMEOUT);
}
//******************** 對外提供方法 ***********************//
/**
* 返回future值用來獲取調用線程池返回的結果
* @param c Callable接口實現類實例
* @return
*/
public static Future<?> submit(Callable<?> c) {
return threadPool.submit(c);
}
/**
* 不關心返回結果
* @param r
*/
public static void execute(Runnable r) {
threadPool.execute(r);
}
/**
* 獲取當前線程池線程數量
* @return
*/
public static int getSize() {
return threadPool.getPoolSize();
}
/**
* 獲取當前活動的線程數量
* @return
*/
public static int getActiveCount() {
return threadPool.getActiveCount();
}
}
併發工具類及線程池小結
可歸納爲幾類
-
容器類 :ConcurrentHashMap、ConcurrentSkipListMap、CopyOnWriteArrayList、CopyOnWriteArraySet
-
隊列 :ConcurrentLinkedQueue、BlockingQueue
-
同步結構 :countDownLatch、CyclicBarrier
-
Executor框架
挑重點說
ThreadPoolExecutor
主要屬性:
核心線程數 : 核心線程數 最大線程數 : 最大線程數 線程存活時間 :一般指的是非核心線程,當然核心線程也可以通過設置來決定是否有存活時間
線程工廠 : 用來生產線程的工廠類
拒絕任務的策略:線程池滿的時候的拒絕策略。默認是返回異常。
阻塞隊列
額外擴展學習了跳錶的數據結構、CopyOnWrite機制
本章內容爲知識點的理論學習,後續章節學習涉及的一些框架思想並進行實踐練習。