java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)
一、併發隊列
先看全家福
併發隊列又分爲阻塞隊列與非阻塞隊列
- 實現了BlockingQueue的就是阻塞隊列,最下層左邊5個。隊列滿的時候放不進去,隊列空的時候null都取不出來,會阻塞。
- 最右邊2個就是非阻塞隊列。
以* Deque結尾的是雙端隊列,頭和尾都能添加和刪除。雙進雙出。一般使用*Queue結尾的。Queue只能一段進一端出。
二、阻塞併發隊列
通常,應用於生產者消費者模型。阻塞隊列的一段給生產者用,一段給消費者用。阻塞隊列是線程安全的,所以生產者消費者都可以是多線程的。
方法
- take():獲取並移除隊列頭結點,如果隊列沒有數據,則阻塞,直到隊列裏有數據
- put():插入數據,隊列滿了的話,則阻塞,直到隊列有空閒空間
- add():插入數據,隊列滿了的話,會拋出異常
- remove():刪除數據,隊列爲空的話,會拋出異常
- element():返回隊列頭元素,隊列爲空的話,會拋出異常
- offer():添加一個元素,隊列滿了的話,會返回false
- poll():取一個元素,隊列爲空的話,會返回null。取出的同時會刪除
- peak():同poll一樣,不過取出時不會刪除
1、ArrayBlockingQueue
- 有界的
- 初始化需要指定容量
- 公平:指定公平的話,等待最長時間的線程會優先處理
代碼實例在我的倉庫:https://github.com/MistraR/springboot-advance 包:com.advance.mistra.test.juc.queue
put方法
public void put(E e) throws InterruptedException {
// 判空
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
// 加鎖,等待過程中可以被中斷
lock.lockInterruptibly();
try {
while (count == items.length)
// 如果隊列滿了,則阻塞等待。這個nofFull是在初始化時生成的Condition對象
notFull.await();
// 隊列沒滿,則入隊
enqueue(e);
} finally {
// 解鎖
lock.unlock();
}
}
// ArrayBlockingQueue的部分屬性
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
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();
}
2、LinkedBlockingQueue
- 無界的,最大爲Integer.MAX_VALUE
- 結構:Node包裝元素,有兩把鎖,takeLock和putLock
LinkedBlockingQueue
// 部分初始化參數
// 最大容量
private final int capacity;
// 原子類存儲當前隊列大小
private final AtomicInteger count = new AtomicInteger();
// 有2把鎖,put和take互不干擾
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
// 內部類Node
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
put方法
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
// 使用的put鎖
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
// 如果隊列滿了,則阻塞
notFull.await();
}
// 隊列沒滿,則入隊
enqueue(node);
// count+1
c = count.getAndIncrement();
if (c + 1 < capacity)
// 當前容量還沒有滿,則喚醒一個在等待的put線程
notFull.signal();
} finally {
// 釋放put鎖
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
3、PriorityBlockingQueue
- 無界的
- 支持優先級,不是先進先出
- 自然排序的,插入的元素必須是可比較的
4、SynchronousQueue
- 容量爲0,不存儲數據,只做直接交換
- 是Executors.newCachedThreadPool()使用的阻塞隊列
5、DelayQueue
- 無界的
- 根據延遲時間排序
- 元素必須實現Delayed接口,規定排序規則
三、非阻塞併發隊列
1、ConcurrentLinkedQueue
使用鏈表的結構,使用CAS非阻塞算法來保證線程安全。跟阻塞隊列用ReentrantLock保證併發安全不同。
offer方法
public boolean offer(E e) {
final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
// for死循環
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
// p是尾節點,newNode就是Node保證之後的元素。
// 直接CAS設置尾節點爲newNode,設置失敗則循環,直到CAS成功
if (NEXT.compareAndSet(p, 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; failure is OK
TAIL.weakCompareAndSet(this, t, newNode);
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;
}
}
- 我的公衆號:Coding摳腚
- 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。