ConcurrentLinkedQueue性能好於BlockingQueue
ConcurrentLinkedQueue重要方法:
- add()和offer()都是加入元素的方法,在ConcurrentLinkedQueue中,這兩個方法沒有任何區別。
- poll()和peek()都是取頭元素節點,區別在於前者會刪除元素,後者不會
入隊列過程
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
try {
//入隊列中斷,發出notFull新型號,表下一個入隊列線程能喚醒
while (count.get() == capacity)
notFull.await();
} catch (InterruptedException ie) {
//入隊成功,隊列不滿補一個notFull信號,減少上下文切換次數,每次喚醒都隨機喚醒一個,發送個信號,讓其他知道隊不滿
notFull.signal(); // propagate to a non-interrupted thread
throw ie;
}
insert(e);
//用臨時變量開銷小
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
入隊列方法
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
// 如果e爲null,則直接拋出NullPointerException異常
checkNotNull(e);
// 創建入隊節點
final Node<E> newNode = new Node<E>(e);
// 循環CAS直到入隊成功
// 1、根據tail節點定位出尾節點(last node);2、將新節點置爲尾節點的下一個節點;3、casTail更新尾節點
for (Node<E> t = tail, p = t;;) {
// p用來表示隊列的尾節點,初始情況下等於tail節點
// q是p的next節點
Node<E> q = p.next;
// 判斷p是不是尾節點,tail節點不一定是尾節點,判斷是不是尾節點的依據是該節點的next是不是null
// 如果p是尾節點
if (q == null) {
// p is last node
// 設置p節點的下一個節點爲新節點,設置成功則casNext返回true;否則返回false,說明有其他線程更新過尾節點
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".
// 如果p != t,則將入隊節點設置成tail節點,更新失敗了也沒關係,因爲失敗了表示有其他線程成功更新了tail節點
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
}
// 多線程操作時候,由於poll時候會把舊的head變爲自引用,然後將head的next設置爲新的head
// 所以這裏需要重新找新的head,因爲新的head後面的節點纔是激活的節點
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 E poll() {
restartFromHead:
for (;;) {
// p節點表示首節點,即需要出隊的節點
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 如果p節點的元素不爲null,則通過CAS來設置p節點引用的元素爲null,如果成功則返回p節點的元素
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
// 如果p != h,則更新head
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// 如果頭節點的元素爲空或頭節點發生了變化,這說明頭節點已經被另外一個線程修改了。
// 那麼獲取p節點的下一個節點,如果p節點的下一節點爲null,則表明隊列已經空了
else if ((q = p.next) == null) {
// 更新頭結點
updateHead(h, p);
return null;
}
// p == q,則使用新的head重新開始
else if (p == q)
continue restartFromHead;
// 如果下一個元素不爲空,則將頭節點的下一個節點設置成頭節點
else
p = q;
}
}
}
這是因爲會調用updateHead方法
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
// 將舊的頭結點h的next域指向爲h
h.lazySetNext(h);
}
在更新完head之後,會將舊的頭結點h的next域指向爲h,上圖中所示的虛線也就表示這個節點的自引用。
阻塞隊列
向隊列中添加元素時,隊列的長度已滿阻塞當前添加線程,直到隊列未滿或者等待超時;
從隊列中獲取元素時,隊列中元素爲空 ,會將獲取元素的線程阻塞,直到隊列中存在元素 或者等待超時。
puts操作
add(E e) : 添加成功返回true,失敗拋IllegalStateException異常
offer(E e) : 成功返回 true,如果此隊列已滿,則返回 false(如果添加了時間參數,且隊列已滿也會阻塞)
put(E e) :將元素插入此隊列的尾部,如果該隊列已滿,則一直阻塞
takes操作
remove(Object o) :移除指定元素,成功返回true,失敗返回false
poll() : 獲取並移除此隊列的頭元素,若隊列爲空,則返回 null(如果添加了時間參數,且隊列中沒有數據也會阻塞)
take():獲取並移除此隊列頭元素,若沒有元素則一直阻塞。
peek() :獲取但不移除此隊列的頭;若隊列爲空,則返回 null。
other操作
contains(Object o):隊列中是否包含指定元素
drainTo(Collection<? super E> c):隊列轉化爲集合
BlockingQueue常用於生產者消費者,作用是解耦
class Producer implements Runnable {
private final BlockingQueue queue;
private int i;
Producer(BlockingQueue q) {
queue = q;
}
public void run() {
try {
while (true) {
queue.put(produce());// 將產品放入緩衝隊列
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int produce() {
return i++;// 生產產品
}
}
class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) {
queue = q;
}
public void run() {
try {
while (true) {
consume(queue.take());// 從緩衝隊列取出產品
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void consume(Object x) {
System.out.println("消費:"+x);// 消費產品
}
}
public class Runner {
public static void main(String[] args) {
BlockingQueue q = new LinkedBlockingQueue<Integer>(10);// 或其他實現
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
ArrayBlockingQueue 類實現了 BlockingQueue 接口。其次ArrayBlockingQueue 是一個有界的阻塞隊列,其內部實現是將對象放到一個數組裏,所以一旦創建了該隊列,就不能再增加其容量了。最後ArrayBlockingQueue 內部以 FIFO(先進先出)的順序對元素進行存儲。
/** The queued items */
final Object[] items; //利用數組來存儲元素
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty; //定義一個Condition對象,用來對take進行操作
/** Condition for waiting puts */
private final Condition notFull; //定義一個Condition對象,用來對put進行操作
/**
* Creates an {@code ArrayBlockingQueue} with the given (fixed)
* capacity and the specified access policy.
*
* @param capacity the capacity of this queue
* @param fair if {@code true} then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if {@code false} the access order is unspecified.
* @throws IllegalArgumentException if {@code capacity < 1}
*/
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();
}
====================================put()方法==============================================
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await(); //隊列飽滿時,將使這個線程進入阻塞狀態,直到被其他線程喚醒時插入元素
enqueue(e); //將元素插入到隊列中
} finally {
lock.unlock();
}
}
/**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
// 先進先出原則
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal(); //通知take那邊消費其元素
}
===================================================take()方法========================================================
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock; //加鎖
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); //隊列爲空時,將使這個線程進入阻塞狀態,直到被其他線程喚醒時取出元素
return dequeue(); //消費對頭中的元素
} finally {
lock.unlock();
}
}
/**
* Extracts element at current take position, advances, and signals.
* Call only when holding lock.
*/
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
//移除節點將內存職位空,防止內存泄漏
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //通知put那邊消費其元素
return x;
}
LinkedBlockingQueue
實現了 BlockingQueue 接口。同時LinkedBlockingQueue 內部以一個鏈式結構(鏈接節點)對其元素進行存儲。如果需要的話,這一鏈式結構可以選擇一個上限。如果沒有定義上限,將使用 Integer.MAX_VALUE 作爲上限。LinkedBlockingQueue 內部以 FIFO(先進先出)的順序對元素進行存儲。
/** The capacity bound, or Integer.MAX_VALUE if none */
//鏈表的容量
private final int capacity;
/** Current number of elements */
//當前元素個數
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.
* Invariant: head.item == null
*/
//鏈表頭節點
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next == null
*/
//鏈表尾節點
private transient Node<E> last;
/** Lock held by take, poll, etc */
//出隊列鎖
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
//入隊列鎖
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
//默認構造方法,默認執行容量上限
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//指定隊列的容量
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//初始化頭尾節點的值,設置均爲null
last = head = new Node<E>(null);
}
//往對尾中插入元素,隊列滿時,則會發生阻塞,直到有元素消費了或者線程中斷了
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;//入隊列鎖
final AtomicInteger count = this.count;//獲取當前隊列中的元素個數
putLock.lockInterruptibly();
try {
while (count.get() == capacity) { //條件:如果隊列滿了
notFull.await(); //則加入到出隊列等待中,直到隊列不滿了,這時就會被其他線程notFull.signal()喚醒
}
enqueue(node);//將元素入隊列
c = count.getAndIncrement(); //對當前隊列元素個數加1
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
//出隊列,大致原理與入隊列相反,當隊列爲空時,則會阻塞,直到隊列不爲空或者線程中斷
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
1、入隊列時,當隊列滿了,則會發生阻塞,直到隊列消費了數據或者線程被中斷了纔會喚醒
2、出隊列時,當隊列爲空時,則會發生阻塞,直到隊列中有數據了或者線程被中斷了纔會喚醒
源碼注意:
ArrayBlockingQueue源碼中,共用的是同一把鎖
LinkedBlockingQueue源碼中,則是用到了兩把鎖,一把是入隊列鎖,另一把是出隊列鎖,讀寫可以同時執行,提高了吞吐量
討論看:關於雙鎖對比。
爲何ArrayBlockingQueue用單鎖
兩者區別:
ArrayBlockingQueue實現的隊列中在生產和消費的時候,是直接將枚舉對象插入或移除的;ArrayBlockingQueue基於數組,在生產和消費的時候,是直接將枚舉對象插入或移除的,不會產生或銷燬任何額外的對象實例;
LinkedBlockingQueue實現的隊列中在生產和消費的時候,需要把枚舉對象轉換爲Node進行插入或移除,會影響性能;LinkedBlockingQueue基於鏈表,在生產和消費的時候,需要把枚舉對象轉換爲Node進行插入或移除,會生成一個額外的Node對象,這 在長時間內需要高效併發地處理大批量數據的系統中,其對於GC的影響還是存在一定的區別。
在使用LinkedBlockingQueue時,若用默認大小且當生產速度大於消費速度時候,有可能會內存溢出。
在使用ArrayBlockingQueue和LinkedBlockingQueue分別對1000000個簡單字符做入隊操作時,
LinkedBlockingQueue的消耗是ArrayBlockingQueue消耗的10倍左右,
即LinkedBlockingQueue消耗在1500毫秒左右,而ArrayBlockingQueue只需150毫秒左右。
SynchronousQueue:
SynchronousQueue 也是一個隊列來的,但它的特別之處在於它內部沒有容器,一個生產線程,當它生產產品(即put的時候),如果當前沒有人想要消費產品(即當前沒有線程執行take),此生產線程必須阻塞,等待一個消費線程調用take操作,take操作將會喚醒該生產線程,同時消費線程會獲取生產線程的產品
數據結構:
說明:數據結構有兩種類型,棧和隊列;棧有一個頭結點,隊列有一個頭結點和尾結點;棧用於實現非公平策略,隊列用於實現公平策略。
公平模式:隊列的先進先出put1再put2,再take1
非公平模式:棧先進後出
看看源碼
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)) {//1
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available, and uses the provided
* ThreadFactory to create new threads when needed.
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
* @throws NullPointerException if threadFactory is null
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public class SynchronousQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//Transferer是一個抽象類,SynchronousQueue內部有2個Transferer的子類,分別是TransferQueue和TransferStack
//
private transient volatile Transferer<E> transferer;
//默認構造方法的線程等待隊列是不保證順序的
public SynchronousQueue() {
this(false);
}
//如果fair爲true,那SynchronousQueue所採用的是能保證先進先出的TransferQueue,也就是先被掛起的線程會先返回
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
//向SynchronousQueue中添加數據,如果此時線程隊列中沒有獲取數據的線程的話,當前的線程就會掛起等待
public void put(E e) throws InterruptedException {
//添加的數據不能是null
if (e == null) throw new NullPointerException();
//可以看到添加的方法調用的是transfer方法,如果添加失敗會拋出InterruptedException異常
//後面我們可以在transfer方法的源碼中調用put方法添加數據在當前線程被中斷時纔會返回null
//這裏相當於繼續把線程中斷的InterruptedException向上拋出
if (transferer.transfer(e, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
//不帶超時時間的offer方法,如果此時沒有線程正在等待獲取數據的話transfer就會返回null,也就是添加數據失敗
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
return transferer.transfer(e, true, 0) != null;
}
//帶超時時間的offer方法,與上面的不同的是這個方法會等待一個超時時間,如果時間過了還沒有線程來獲取數據就會返回失敗
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
//添加的數據被其他線程成功獲取,返回成功
if (transferer.transfer(e, true, unit.toNanos(timeout)) != null)
return true;
//如果添加數據失敗了,有可能是線程被中斷了,不是的話直接返回false
if (!Thread.interrupted())
return false;
//是線程被中斷的話就向上跑出InterruptedException異常
throw new InterruptedException();
}
//take方法用於從隊列中取數據,如果此時沒有添加數據的線程被掛起,那當前線程就會被掛起等待
public E take() throws InterruptedException {
E e = transferer.transfer(null, false, 0);
//成功獲取數據
if (e != null)
return e;
//沒有獲取到數據,同時又退出掛起狀態了,那說明線程被中斷了,向上拋出InterruptedException
Thread.interrupted();
throw new InterruptedException();
}
//poll方法同樣用於獲取數據
public E poll() {
return transferer.transfer(null, true, 0);
}
//帶超時時間的poll方法,如果超時時間到了還沒有線程插入數據,就會返回失敗
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E e = transferer.transfer(null, true, unit.toNanos(timeout));
//返回結果有2種情況
//e != null表示成功取到數據了
//!Thread.interrupted()表示返回失敗了,且是因爲超時失敗的,此時e是null
if (e != null || !Thread.interrupted())
return e;
//返回失敗了,並且是因爲當前線程被中斷了
throw new InterruptedException();
}
//可以看到SynchronousQueue的isEmpty方法一直返回的是true,因爲SynchronousQueue沒有任何容量
public boolean isEmpty() {
return true;
}
//同樣的size方法也返回0
public int size() {
return 0;
}
<!--下面我們看看TransferQueue的具體實現,TransferQueue中的關鍵方法就是transfer方法了-->
//先看看TransferQueue的父類Transferer,比較簡單,就是提供了一個transfer方法,需要子類具體實現
abstract static class Transferer<E> {
abstract E transfer(E e, boolean timed, long nanos);
}
//TransferQueue
static final class TransferQueue<E> extends Transferer<E> {
//內部的節點類,用於表示一個請求
//這裏可以看出TransferQueue內部是一個單鏈表,因此可以保證先進先出
static final class QNode {
volatile QNode next; // next node in queue
volatile Object item; // CAS'ed to or from null
//請求所在的線程
volatile Thread waiter; // to control park/unpark
//用於判斷是入隊還是出隊,true表示的是入隊操作,也就是添加數據
final boolean isData;
QNode(Object item, boolean isData) {
this.item = item;
this.isData = isData;
}
//可以看到QNode內部通過volatile關鍵字以及Unsafe類的CAS方法來實現線程安全
//compareAndSwapObject方法第一個參數表示需要改變的對象,第二個參數表示偏移量
//第三個參數表示參數期待的值,第四個參數表示更新後的值
//下面的方法調用的意思是將當前的QNode對象(this)的next字段賦值爲val,當目前的next的值是cmp時就會更新next字段成功
boolean casNext(QNode cmp, QNode val) {
return next == cmp &&
U.compareAndSwapObject(this, NEXT, cmp, val);
}
//方法的原理同上面的類似,這裏就是更新item的值了
boolean casItem(Object cmp, Object val) {
return item == cmp &&
U.compareAndSwapObject(this, ITEM, cmp, val);
}
//方法的原理同上面的類似,這裏把item賦值爲自己,就表示取消當前節點表示的操作了
void tryCancel(Object cmp) {
U.compareAndSwapObject(this, ITEM, cmp, this);
}
//調用tryCancel方法後item就會是this,就表示當前任務被取消了
boolean isCancelled() {
return item == this;
}
//表示當前任務已經被返回了
boolean isOffList() {
return next == this;
}
// Unsafe mechanics
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long ITEM;
private static final long NEXT;
static {
try {
ITEM = U.objectFieldOffset
(QNode.class.getDeclaredField("item"));
NEXT = U.objectFieldOffset
(QNode.class.getDeclaredField("next"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
//首節點
transient volatile QNode head;
//尾部節點
transient volatile QNode tail;
/**
* Reference to a cancelled node that might not yet have been
* unlinked from queue because it was the last inserted node
* when it was cancelled.
*/
transient volatile QNode cleanMe;
//構造函數中會初始化一個出隊的節點,並且首尾都指向這個節點
TransferQueue() {
QNode h = new QNode(null, false); // initialize to dummy node.
head = h;
tail = h;
}
/**
* Puts or takes an item
* 主方法
*
* @param e if non-null, the item to be handed to a consumer;
* if null, requests that transfer return an item
* offered by producer.
* @param timed if this operation should timeout
* @param nanos the timeout, in nanosecond
* @return
*/
@Override
E transfer(E e, boolean timed, long nanos) {
/**
* Basic algorithm is to loop trying to take either of
* two actions:
*
* 1. If queue apparently empty or holding same-mode nodes,
* try to add node to queue of waiters, wait to be
* fulfilled (or cancelled) and return matching item.
*
* 2. If queue apparently contains waiting items, and this
* call is of complementary mode, try to fulfill by CAS'ing
* item field of waiting node and dequeuing it, and then
* returning matching item.
*
* In each case, along the way, check for gurading against
* seeing uninitialized head or tail value. This never
* happens in current SynchronousQueue, but could if
* callers held non-volatile/final ref to the
* transferer. The check is here anyway because it places
* null checks at top of loop, which is usually faster
* than having them implicity interspersed
*
* 這個 producer / consumer 的主方法, 主要分爲兩種情況
*
* 1. 若隊列爲空 / 隊列中的尾節點和自己的 類型相同, 則添加 node
* 到隊列中, 直到 timeout/interrupt/其他線程和這個線程匹配
* timeout/interrupt awaitFulfill方法返回的是 node 本身
* 匹配成功的話, 要麼返回 null (producer返回的), 或正真的傳遞值 (consumer 返回的)
*
* 2. 隊列不爲空, 且隊列的 head.next 節點是當前節點匹配的節點,
* 進行數據的傳遞匹配, 並且通過 advanceHead 方法幫助 先前 block 的節點 dequeue
*/
QNode s = null; // constrcuted/reused as needed
boolean isData = (e != null); // 1.判斷 e != null 用於區分 producer 與 consumer
for(;;){
QNode t = tail;
QNode h = head;
if(t == null || h == null){ // 2. 數據未初始化, continue 重來
continue; // spin
}
if(h == t || t.isData == isData){ // 3. 隊列爲空, 或隊列尾節點和自己相同 (注意這裏是和尾節點比價, 下面進行匹配時是和 head.next 進行比較)
QNode tn = t.next;
if(t != tail){ // 4. tail 改變了, 重新再來
continue;
}
if(tn != null){ // 5. 其他線程添加了 tail.next, 所以幫助推進 tail
advanceTail(t, tn);
continue;
}
if(timed && nanos <= 0){ // 6. 調用的方法的 wait 類型的, 並且 超時了, 直接返回 null, 直接見 SynchronousQueue.poll() 方法,說明此 poll 的調用只有當前隊列中正好有一個與之匹配的線程在等待被【匹配纔有返回值
return null;
}
if(s == null){
s = new QNode(e, isData); // 7. 構建節點 QNode
}
if(!t.casNext(null, s)){ // 8. 將 新建的節點加入到 隊列中
continue;
}
advanceTail(t, s); // 9. 幫助推進 tail 節點
Object x = awaitFulfill(s, e, timed, nanos); // 10. 調用awaitFulfill, 若節點是 head.next, 則進行一些自旋, 若不是的話, 直接 block, 知道有其他線程 與之匹配, 或它自己進行線程的中斷
if(x == s){ // 11. 若 (x == s)節點s 對應額線程 wait 超時 或線程中斷, 不然的話 x == null (s 是 producer) 或 是正真的傳遞值(s 是 consumer)
clean(t, s); // 12. 對接點 s 進行清除, 若 s 不是鏈表的最後一個節點, 則直接 CAS 進行 節點的刪除, 若 s 是鏈表的最後一個節點, 則 要麼清除以前的 cleamMe 節點(cleamMe != null), 然後將 s.prev 設置爲 cleanMe 節點, 下次進行刪除 或直接將 s.prev 設置爲cleanMe
return null;
}
if(!s.isOffList()){ // 13. 節點 s 沒有 offlist
advanceHead(t, s); // 14. 推進head 節點, 下次就調用 s.next 節點進行匹配(這裏調用的是 advanceHead, 因爲代碼能執行到這邊說明s已經是 head.next 節點了)
if(x != null){ // and forget fields
s.item = s;
}
s.waiter = null; // 15. 釋放線程 ref
}
return (x != null) ? (E)x :e;
}else{ // 16. 進行線程的匹配操作, 匹配操作是從 head.next 開始匹配 (注意 隊列剛開始構建時 有個 dummy node, 而且 head 節點永遠是個 dummy node 這個和 AQS 中一樣的)
QNode m = h.next; // 17. 獲取 head.next 準備開始匹配
if(t != tail || m == null || h != head){
continue; // 18. 不一致讀取, 有其他線程改變了隊列的結構inconsistent read
}
/** producer 和 consumer 匹配操作
* 1. 獲取 m的 item (注意這裏的m是head的next節點
* 2. 判斷 isData 與x的模式是否匹配, 只有produce與consumer才能配成一對
* 3. x == m 判斷是否 節點m 是否已經進行取消了, 具體看(QNOde#tryCancel)
* 4. m.casItem 將producer與consumer的數據進行交換 (這裏存在併發時可能cas操作失敗的情況)
* 5. 若 cas操作成功則將h節點dequeue
*
* 疑惑: 爲什麼將h進行 dequeue, 而不是 m節點
* 答案: 因爲每次進行配對時, 都是將 h 是個 dummy node, 正真的數據節點 是 head.next
*/
Object x = m.item;
if(isData == (x != null) || // 19. 兩者的模式是否匹配 (因爲併發環境下 有可能其他的線程強走了匹配的節點)
x == m || // 20. m 節點 線程中斷或者 wait 超時了
!m.casItem(x, e) // 21. 進行 CAS 操作 更改等待線程的 item 值(等待的有可能是 concumer / producer)
){
advanceHead(h, m); // 22.推進 head 節點 重試 (尤其 21 操作失敗)
continue;
}
advanceHead(h, m); // 23. producer consumer 交換數據成功, 推進 head 節點
LockSupport.unpark(m.waiter); // 24. 換線等待中的 m 節點, 而在 awaitFulfill 方法中 因爲 item 改變了, 所以 x != e 成立, 返回
return (x != null) ? (E)x : e; // 25. 操作到這裏若是 producer, 則 x != null, 返回 x, 若是consumer, 則 x == null,.返回 producer(其實就是 節點m) 的 e
}
}
}
// 自旋或者等待,直到填充完畢
// 這裏的策略是什麼呢?如果自旋次數不夠了,通常是 16 次,但還有超過 1 秒的時間,就阻塞等待被喚醒。
// 如果時間到了,就取消這次的入隊行爲。
// 返回的是 Node 本身
// s.item 就是 e
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
int spins = ((head.next == s) ?// 如果成功將 tail.next 覆蓋了 tail,如果有超時機制,則自旋 32 次,如果沒有超時機制,則自旋 32 *16 = 512次
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
if (w.isInterrupted())// 當前線程被中斷
s.tryCancel(e);// 嘗試取消這個 item
Object x = s.item;// 獲取到這個 tail 的 item
if (x != e) // 如果不相等,說明 node 中的 item 取消了,返回這個 item。
// 這裏是唯一停止循環的地方。當 s.item 已經不是當初的哪個 e 了,說明要麼是時間到了被取消了,要麼是線程中斷被取消了。
// 當然,不僅僅只有這2種 “意外” 情況,還有一種情況是:當另一個線程拿走了這個數據,並修改了 item,也會通過這個判斷,返回被“修改”過的 item。
return x;
if (timed) {// 如果有時間限制
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {// 如果時間到了
s.tryCancel(e);// 嘗試取消 item,供上面的 x != e 判斷
continue;// 重來
}
}
if (spins > 0)// 如果還有自旋次數
--spins;// 減一
else if (s.waiter == null)// 如果自旋不夠,且 tail 的等待線程還沒有賦值
s.waiter = w;// 當前線程賦值給 tail 的等待線程
else if (!timed)// 如果自旋不夠,且如果線程賦值過了,且沒有限制時間,則 wait,(危險操作)
LockSupport.park(this);
else if (nanos > spinForTimeoutThreshold)// 如果自旋不夠,且如果限制了時間,且時間還剩餘超過 1 秒,則 wait 剩餘時間。
// 主要目的就是等待,等待其他線程喚醒這個節點所在的線程。
LockSupport.parkNanos(this, nanos);
}
}
<!--下面看看執行掛起線程的方法awaitFulfill-->
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
//首先獲取超時時間
final long deadline = timed ? System.nanoTime() + nanos : 0L;
//當前操作所在的線程
Thread w = Thread.currentThread();
//線程被掛起或是進入超時等待之前阻止自旋的次數
int spins = (head.next == s)
? (timed ? MAX_TIMED_SPINS : MAX_UNTIMED_SPINS)
: 0;
for (;;) {
//這裏首先判斷線程是否被中斷了,如果被中斷了就取消等待,並設置s的item指向s本身作爲標記
if (w.isInterrupted())
s.tryCancel(e);
Object x = s.item;
//x != e就表示超時時間到了或是線程被中斷了,也就是執行了tryCancel方法
if (x != e)
return x;
//這裏先判斷超時的時間是否過了
if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
s.tryCancel(e);
continue;
}
}
//這裏通過多幾次循環來避免直接掛起線程
if (spins > 0)
--spins;
else if (s.waiter == null)
s.waiter = w;
else if (!timed)
//park操作會讓線程掛起進入等待狀態(Waiting),需要其他線程調用unpark方法喚醒
LockSupport.park(this);
else if (nanos > SPIN_FOR_TIMEOUT_THRESHOLD)
//parkNanos操作會讓線程掛起進入限期等待(Timed Waiting),不用其他線程喚醒,時間到了會被系統喚醒
LockSupport.parkNanos(this, nanos);
}
}
}
}
SynchronousQueue的一個使用場景是在線程池裏。Executors.newCachedThreadPool()就使用了SynchronousQueue,這個線程池根據需要(新任務到來時)創建新的線程,如果有空閒線程則會重複使用,線程空閒了60秒後會被回收。
SynchronousQueue:這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,如果所有線程都在工作怎麼辦?那就新建一個線程來處理這個任務!所以爲了保證不出現<線程數達到了maximumPoolSize而不能新建線程>的錯誤,使用這個類型隊列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大。
所以,使用SynchronousQueue作爲工作隊列,工作隊列本身並不限制待執行的任務的數量。但此時需要限定線程池的最大大小爲一個合理的有限值,而不是Integer.MAX_VALUE,否則可能導致線程池中的工作者線程的數量一直增加到系統資源所無法承受爲止。
使用SynchronousQueue的目的就是保證“對於提交的任務,如果有空閒線程,則使用空閒線程來處理;否則新建一個線程來處理任務”。
/**
* Puts or takes an item
* 主方法
*
* @param e if non-null, the item to be handed to a consumer;
* if null, requests that transfer return an item
* offered by producer.
* @param timed if this operation should timeout
* @param nanos the timeout, in nanosecond
* @return
*/
@Override
E transfer(E e, boolean timed, long nanos) {
/**
* Basic algorithm is to loop trying to take either of
* two actions:
*
* 1. If queue apparently empty or holding same-mode nodes,
* try to add node to queue of waiters, wait to be
* fulfilled (or cancelled) and return matching item.
*
* 2. If queue apparently contains waiting items, and this
* call is of complementary mode, try to fulfill by CAS'ing
* item field of waiting node and dequeuing it, and then
* returning matching item.
*
* In each case, along the way, check for gurading against
* seeing uninitialized head or tail value. This never
* happens in current SynchronousQueue, but could if
* callers held non-volatile/final ref to the
* transferer. The check is here anyway because it places
* null checks at top of loop, which is usually faster
* than having them implicity interspersed
*
* 這個 producer / consumer 的主方法, 主要分爲兩種情況
*
* 1. 若隊列爲空 / 隊列中的尾節點和自己的 類型相同, 則添加 node
* 到隊列中, 直到 timeout/interrupt/其他線程和這個線程匹配
* timeout/interrupt awaitFulfill方法返回的是 node 本身
* 匹配成功的話, 要麼返回 null (producer返回的), 或正真的傳遞值 (consumer 返回的)
*
* 2. 隊列不爲空, 且隊列的 head.next 節點是當前節點匹配的節點,
* 進行數據的傳遞匹配, 並且通過 advanceHead 方法幫助 先前 block 的節點 dequeue
*/
QNode s = null; // constrcuted/reused as needed
boolean isData = (e != null); // 1.判斷 e != null 用於區分 producer 與 consumer
for(;;){
QNode t = tail;
QNode h = head;
if(t == null || h == null){ // 2. 數據未初始化, continue 重來
continue; // spin
}
if(h == t || t.isData == isData){ // 3. 隊列爲空, 或隊列尾節點和自己相同 (注意這裏是和尾節點比價, 下面進行匹配時是和 head.next 進行比較)
QNode tn = t.next;
if(t != tail){ // 4. tail 改變了, 重新再來
continue;
}
if(tn != null){ // 5. 其他線程添加了 tail.next, 所以幫助推進 tail
advanceTail(t, tn);
continue;
}
if(timed && nanos <= 0){ // 6. 調用的方法的 wait 類型的, 並且 超時了, 直接返回 null, 直接見 SynchronousQueue.poll() 方法,說明此 poll 的調用只有當前隊列中正好有一個與之匹配的線程在等待被【匹配纔有返回值
return null;
}
if(s == null){
s = new QNode(e, isData); // 7. 構建節點 QNode
}
if(!t.casNext(null, s)){ // 8. 將 新建的節點加入到 隊列中
continue;
}
advanceTail(t, s); // 9. 幫助推進 tail 節點
Object x = awaitFulfill(s, e, timed, nanos); // 10. 調用awaitFulfill, 若節點是 head.next, 則進行一些自旋, 若不是的話, 直接 block, 知道有其他線程 與之匹配, 或它自己進行線程的中斷
if(x == s){ // 11. 若 (x == s)節點s 對應額線程 wait 超時 或線程中斷, 不然的話 x == null (s 是 producer) 或 是正真的傳遞值(s 是 consumer)
clean(t, s); // 12. 對接點 s 進行清除, 若 s 不是鏈表的最後一個節點, 則直接 CAS 進行 節點的刪除, 若 s 是鏈表的最後一個節點, 則 要麼清除以前的 cleamMe 節點(cleamMe != null), 然後將 s.prev 設置爲 cleanMe 節點, 下次進行刪除 或直接將 s.prev 設置爲cleanMe
return null;
}
if(!s.isOffList()){ // 13. 節點 s 沒有 offlist
advanceHead(t, s); // 14. 推進head 節點, 下次就調用 s.next 節點進行匹配(這裏調用的是 advanceHead, 因爲代碼能執行到這邊說明s已經是 head.next 節點了)
if(x != null){ // and forget fields
s.item = s;
}
s.waiter = null; // 15. 釋放線程 ref
}
return (x != null) ? (E)x :e;
}else{ // 16. 進行線程的匹配操作, 匹配操作是從 head.next 開始匹配 (注意 隊列剛開始構建時 有個 dummy node, 而且 head 節點永遠是個 dummy node 這個和 AQS 中一樣的)
QNode m = h.next; // 17. 獲取 head.next 準備開始匹配
if(t != tail || m == null || h != head){
continue; // 18. 不一致讀取, 有其他線程改變了隊列的結構inconsistent read
}
/** producer 和 consumer 匹配操作
* 1. 獲取 m的 item (注意這裏的m是head的next節點
* 2. 判斷 isData 與x的模式是否匹配, 只有produce與consumer才能配成一對
* 3. x == m 判斷是否 節點m 是否已經進行取消了, 具體看(QNOde#tryCancel)
* 4. m.casItem 將producer與consumer的數據進行交換 (這裏存在併發時可能cas操作失敗的情況)
* 5. 若 cas操作成功則將h節點dequeue
*
* 疑惑: 爲什麼將h進行 dequeue, 而不是 m節點
* 答案: 因爲每次進行配對時, 都是將 h 是個 dummy node, 正真的數據節點 是 head.next
*/
Object x = m.item;
if(isData == (x != null) || // 19. 兩者的模式是否匹配 (因爲併發環境下 有可能其他的線程強走了匹配的節點)
x == m || // 20. m 節點 線程中斷或者 wait 超時了
!m.casItem(x, e) // 21. 進行 CAS 操作 更改等待線程的 item 值(等待的有可能是 concumer / producer)
){
advanceHead(h, m); // 22.推進 head 節點 重試 (尤其 21 操作失敗)
continue;
}
advanceHead(h, m); // 23. producer consumer 交換數據成功, 推進 head 節點
LockSupport.unpark(m.waiter); // 24. 換線等待中的 m 節點, 而在 awaitFulfill 方法中 因爲 item 改變了, 所以 x != e 成立, 返回
return (x != null) ? (E)x : e; // 25. 操作到這裏若是 producer, 則 x != null, 返回 x, 若是consumer, 則 x == null,.返回 producer(其實就是 節點m) 的 e
}
}
}
// 自旋或者等待,直到填充完畢
// 這裏的策略是什麼呢?如果自旋次數不夠了,通常是 16 次,但還有超過 1 秒的時間,就阻塞等待被喚醒。
// 如果時間到了,就取消這次的入隊行爲。
// 返回的是 Node 本身
// s.item 就是 e
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
int spins = ((head.next == s) ?// 如果成功將 tail.next 覆蓋了 tail,如果有超時機制,則自旋 32 次,如果沒有超時機制,則自旋 32 *16 = 512次
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
if (w.isInterrupted())// 當前線程被中斷
s.tryCancel(e);// 嘗試取消這個 item
Object x = s.item;// 獲取到這個 tail 的 item
if (x != e) // 如果不相等,說明 node 中的 item 取消了,返回這個 item。
// 這裏是唯一停止循環的地方。當 s.item 已經不是當初的哪個 e 了,說明要麼是時間到了被取消了,要麼是線程中斷被取消了。
// 當然,不僅僅只有這2種 “意外” 情況,還有一種情況是:當另一個線程拿走了這個數據,並修改了 item,也會通過這個判斷,返回被“修改”過的 item。
return x;
if (timed) {// 如果有時間限制
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {// 如果時間到了
s.tryCancel(e);// 嘗試取消 item,供上面的 x != e 判斷
continue;// 重來
}
}
if (spins > 0)// 如果還有自旋次數
--spins;// 減一
else if (s.waiter == null)// 如果自旋不夠,且 tail 的等待線程還沒有賦值
s.waiter = w;// 當前線程賦值給 tail 的等待線程
else if (!timed)// 如果自旋不夠,且如果線程賦值過了,且沒有限制時間,則 wait,(危險操作)
LockSupport.park(this);
else if (nanos > spinForTimeoutThreshold)// 如果自旋不夠,且如果限制了時間,且時間還剩餘超過 1 秒,則 wait 剩餘時間。
// 主要目的就是等待,等待其他線程喚醒這個節點所在的線程。
LockSupport.parkNanos(this, nanos);
}
}
isData 屬性極爲重要,標識這這個線程的這次操作,決定了他到底應該是追加到隊列中,還是從隊列中交換數據。
每個線程在沒有遇到自己的另一半時,要麼快速失敗,要麼進行阻塞,阻塞等待自己的另一半來,至於對方是給數據還是取數據,取決於她自己,如果她是消費者,那麼他就是生產者
ConcurrentLinkedQueue
ConcurrentLinkedQuue
Node節點內部則維護一個volatile 修飾的變量item 用來存放節點的值,next用來存放鏈表的下一個節點,從而鏈接爲一個單向無界鏈表,這就是單向無界的根本原因。如下圖:
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
public boolean offer(E e) {
//(1)e爲null則拋出空指針異常
checkNotNull(e);
//(2)構造Node節點
final Node<E> newNode = new Node<E>(e);
//(3)從尾節點進行插入
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
//(4)如果q==null說明p是尾節點,則執行插入
if (q == null) {
//(5)使用CAS設置p節點的next節點
if (p.casNext(null, newNode)) {
//(6)cas成功,則說明新增節點已經被放入鏈表,然後設置當前尾節點,併發情況下,因爲poll可能會改變這個尾部節點。
if (p != t)
casTail(t, newNode); // Failure is OK.
return true;
}
}
else if (p == q)//(7)
//多線程操作時候,由於poll操作移除元素後有可能會把head變爲自引用,然後head的next變爲新head,所以這裏需要
//重新找新的head,因爲新的head後面的節點纔是正常的節點。
p = (t != (t = tail)) ? t : head;
else
//(8) 尋找尾節點
p = (p != t && t != (t = tail)) ? t : q;
}
}
public E poll() {
//(1) goto標記
restartFromHead:
//(2)無限循環
for (;;) {
for (Node<E> h = head, p = h, q;;) {
//(3)保存當前節點值
E item = p.item;
//(4)當前節點有值則cas變爲null
if (item != null && p.casItem(item, null)) {
//(5)cas成功標誌當前節點以及從鏈表中移除
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
//(6)當前隊列爲空則返回null
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
//(7)自引用了,則重新找新的隊列頭節點
else if (p == q)
continue restartFromHead;
else//(8)
p = q;
}
}
}
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
public E peek() {
//(1)
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
//(2)
E item = p.item;
//(3)
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
//(4)
else if (p == q)
continue restartFromHead;
else
//(5)
p = q;
}
}
}