上篇文章12分鐘從Executor自頂向下徹底搞懂線程池中我們聊到線程池,而線程池中包含阻塞隊(duì)列
這篇文章我們主要聊聊并發(fā)包下的阻塞隊(duì)列
阻塞隊(duì)列
什么是隊(duì)列?
隊(duì)列的實(shí)現(xiàn)可以是數(shù)組、也可以是鏈表,可以實(shí)現(xiàn)先進(jìn)先出的順序隊(duì)列,也可以實(shí)現(xiàn)先進(jìn)后出的棧隊(duì)列
那什么是阻塞隊(duì)列?
在經(jīng)典的生產(chǎn)者/消費(fèi)者模型中,生產(chǎn)者們將生產(chǎn)的元素放入隊(duì)列,而消費(fèi)者們從隊(duì)列獲取元素消費(fèi)
當(dāng)隊(duì)列已滿,我們會(huì)手動(dòng)阻塞生產(chǎn)者,直到消費(fèi)者消費(fèi)再來手動(dòng)喚醒生產(chǎn)者
當(dāng)隊(duì)列為空,我們會(huì)手動(dòng)阻塞消費(fèi)者,直到生產(chǎn)者生產(chǎn)再來手動(dòng)喚醒消費(fèi)者
在這個(gè)過程中由于使用的是普通隊(duì)列,阻塞與喚醒我們需要手動(dòng)操作,保證同步機(jī)制
阻塞隊(duì)列在隊(duì)列的基礎(chǔ)上提供等待/通知功能,用于線程間的通信,避免線程競(jìng)爭(zhēng)死鎖
生產(chǎn)者可以看成往線程池添加任務(wù)的用戶線程,而消費(fèi)者則是線程池中的工作線程
當(dāng)阻塞隊(duì)列為空時(shí)阻塞工作線程獲取任務(wù),當(dāng)阻塞隊(duì)列已滿時(shí)阻塞用戶線程向隊(duì)列中添加任務(wù)(創(chuàng)建非核心線程、拒絕策略)
API
阻塞隊(duì)列提供一下四種添加、刪除元素的API,我們常用阻塞等待/超時(shí)阻塞等待的API
| 方法名 | 拋出異常 | 返回true/false | 阻塞等待 | 超時(shí)阻塞等待 |
|---|---|---|---|---|
| 添加 | add(Object) | offer(Object) | put(Object) | offer(Object,long,TimeUnit) |
| 刪除 | remove() | poll() | take() | poll(long,TimeUnit) |
- 拋出異常:隊(duì)滿add 拋出異常
IllegalStateExceptio;隊(duì)空remove 拋出異常NoSuchElementException - 返回值: 隊(duì)滿offer返回false,隊(duì)空poll返回null
- 阻塞等待: 隊(duì)滿時(shí)put會(huì)阻塞線程 或 隊(duì)空時(shí)take會(huì)阻塞線程
- 超時(shí)阻塞等待: 在阻塞等待、返回true/false的基礎(chǔ)上增加超時(shí)等待(等待一定時(shí)間就退出等待)
阻塞隊(duì)列的公平與不公平
什么是阻塞隊(duì)列的公平與不公平?
當(dāng)阻塞隊(duì)列已滿時(shí),如果是公平的,那么阻塞的線程根據(jù)先后順序從阻塞隊(duì)列中獲取元素,不公平則反之
實(shí)際上阻塞隊(duì)列的公平與不公平,要看實(shí)現(xiàn)阻塞隊(duì)列的鎖是否公平
阻塞隊(duì)列一般默認(rèn)使用不公平鎖
ArrayBlockingQueue
從名稱看就可以知道它是數(shù)組實(shí)現(xiàn)的,我們先來看看它有哪些重要字段
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//存儲(chǔ)元素的數(shù)組
final Object[] items;
//記錄元素出隊(duì)的下標(biāo)
int takeIndex;
//記錄元素入隊(duì)的下標(biāo)
int putIndex;
//隊(duì)列中元素?cái)?shù)量
int count;
//使用的鎖
final ReentrantLock lock;
//出隊(duì)的等待隊(duì)列,作用于消費(fèi)者
private final Condition notEmpty;
//入隊(duì)的等待隊(duì)列,作用于生產(chǎn)者
private final Condition notFull;
}
看完關(guān)鍵字段,我們可以知道:ArrayBlockingQueue由數(shù)組實(shí)現(xiàn)、使用并發(fā)包下的可重入鎖、同時(shí)用兩個(gè)等待隊(duì)列作用生產(chǎn)者和消費(fèi)者
為什么出隊(duì)、入隊(duì)要使用兩個(gè)下標(biāo)記錄?
實(shí)際上它是一個(gè)環(huán)形數(shù)組,在初始化后就不改變大小,后續(xù)查看源碼自然能明白它是環(huán)形數(shù)組
在構(gòu)造器中、初始化數(shù)組容量,同時(shí)使用非公平鎖
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();
}
ArrayBlockingQueue的公平性是由ReentrantLock來實(shí)現(xiàn)的
我們來看看入隊(duì)方法,入隊(duì)方法都大同小異,我們本文都查看支持超時(shí)、響應(yīng)中斷的方法
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//檢查空指針
checkNotNull(e);
//獲取超時(shí)納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//加鎖
lock.lockInterruptibly();
try {
//如果隊(duì)列已滿
while (count == items.length) {
//超時(shí)則返回入隊(duì)失敗,否則生產(chǎn)者等待對(duì)應(yīng)時(shí)間
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
//入隊(duì)
enqueue(e);
return true;
} finally {
//解鎖
lock.unlock();
}
}
直接使用可重入鎖保證同步,如果隊(duì)列已滿,在此期間判斷是否超時(shí),超時(shí)就返回,未超時(shí)等待;未滿則執(zhí)行入隊(duì)方法
private void enqueue(E x) {
//隊(duì)列數(shù)組
final Object[] items = this.items;
//往入隊(duì)下標(biāo)添加值
items[putIndex] = x;
//自增入隊(duì)下標(biāo) 如果已滿則定位到0 成環(huán)
if (++putIndex == items.length)
putIndex = 0;
//統(tǒng)計(jì)數(shù)量增加
count++;
//喚醒消費(fèi)者
notEmpty.signal();
}
在入隊(duì)中,主要是添加元素、修改下次添加的下標(biāo)、統(tǒng)計(jì)隊(duì)列中的元素和喚醒消費(fèi)者,到這以及可以說明它的實(shí)現(xiàn)是環(huán)形數(shù)組
ArrayBlockingQueue由環(huán)形數(shù)組實(shí)現(xiàn)的阻塞隊(duì)列,固定容量不支持動(dòng)態(tài)擴(kuò)容,使用非公平的ReertrantLock保證入隊(duì)、出隊(duì)操作的原子性,使用兩個(gè)等待隊(duì)列存儲(chǔ)等待的生產(chǎn)者、消費(fèi)者,適用于在并發(fā)量不大的場(chǎng)景
LinkedBlockingQueue
LinkedBlockingQueue從名稱上來看,就是使用鏈表實(shí)現(xiàn)的,我們來看看它的關(guān)鍵字段
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//節(jié)點(diǎn)
static class Node<E> {
//存儲(chǔ)元素
E item;
//下一個(gè)節(jié)點(diǎn)
Node<E> next;
//...
}
//容量上限
private final int capacity;
//隊(duì)列元素?cái)?shù)量
private final AtomicInteger count = new AtomicInteger();
//頭節(jié)點(diǎn)
transient Node<E> head;
//尾節(jié)點(diǎn)
private transient Node<E> last;
//出隊(duì)的鎖
private final ReentrantLock takeLock = new ReentrantLock();
//出隊(duì)的等待隊(duì)列
private final Condition notEmpty = takeLock.newCondition();
//入隊(duì)的鎖
private final ReentrantLock putLock = new ReentrantLock();
//入隊(duì)的等待隊(duì)列
private final Condition notFull = putLock.newCondition();
}
從字段中,我們可以知道它使用單向鏈表的節(jié)點(diǎn)、且用首尾節(jié)點(diǎn)記錄隊(duì)列的頭尾,并且它使用兩把鎖、兩個(gè)等待隊(duì)列作用于隊(duì)頭、尾,與ArrayBlockingQueue相比能夠增加并發(fā)性能
有個(gè)奇怪的地方:都使用鎖了,為什么記錄元素?cái)?shù)量count卻使用原子類呢?
這是由于兩把鎖,作用于入隊(duì)與出隊(duì)的操作,入隊(duì)與出隊(duì)也可能并發(fā)執(zhí)行,同時(shí)修改count,因此要使用原子類保證修改數(shù)量的原子性
在初始化時(shí)需要設(shè)置容量大小,否則會(huì)設(shè)置成無界的阻塞隊(duì)列(容量是int的最大值)
當(dāng)消費(fèi)速度小于生產(chǎn)速度時(shí),阻塞隊(duì)列中會(huì)堆積任務(wù),進(jìn)而導(dǎo)致容易發(fā)生OOM
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
來看看入隊(duì)操作
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//加鎖
putLock.lockInterruptibly();
try {
//隊(duì)列已滿,超時(shí)返回,不超時(shí)等待
while (count.get() == capacity) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
//入隊(duì)
enqueue(new Node<E>(e));
// 先獲取再自增 c中存儲(chǔ)的是舊值
c = count.getAndIncrement();
//如果數(shù)量沒滿 喚醒生產(chǎn)者
if (c + 1 < capacity)
notFull.signal();
} finally {
//解鎖
putLock.unlock();
}
//如果舊值為0 說明該入隊(duì)操作前是空隊(duì)列,喚醒消費(fèi)者來消費(fèi)
if (c == 0)
signalNotEmpty();
return true;
}
入隊(duì)操作類似,只不過在此期間如果數(shù)量沒滿喚醒生產(chǎn)者生產(chǎn),隊(duì)列為空喚醒消費(fèi)者來消費(fèi),從而增加并發(fā)性能
入隊(duì)只是改變指向關(guān)系
//添加節(jié)點(diǎn)到末尾
private void enqueue(Node<E> node) {
last = last.next = node;
}
喚醒消費(fèi)者前要先獲取鎖
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
出隊(duì)操作也類似
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 隊(duì)列為空 超時(shí)返回空,否則等待
while (count.get() == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
//出隊(duì)
x = dequeue();
c = count.getAndDecrement();
//隊(duì)列中除了當(dāng)前線程獲取的任務(wù)外還有任務(wù)就去喚醒消費(fèi)者消費(fèi)
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
//原來隊(duì)列已滿就去喚醒生產(chǎn)者 生產(chǎn)
if (c == capacity)
signalNotFull();
return x;
}
LinkedBlockingQueue與ArrayBlockingQueue的出隊(duì)、入隊(duì)實(shí)現(xiàn)類似
只不過LinkedBlockingQueue入隊(duì)、出隊(duì)獲取/釋放的鎖不同,并且在此過程中不同情況回去喚醒其他的生產(chǎn)者、消費(fèi)者從而進(jìn)一步提升并發(fā)性能
LinkedBlockingQueue 由單向鏈表實(shí)現(xiàn)的阻塞隊(duì)列,記錄首尾節(jié)點(diǎn);默認(rèn)是無界、非公平的阻塞隊(duì)列(初始化時(shí)要設(shè)置容量否則可能OOM),使用兩把鎖、兩個(gè)等待隊(duì)列,分別操作入隊(duì)、出隊(duì)的生產(chǎn)者、消費(fèi)者,在入隊(duì)、出隊(duì)操作期間不同情況還會(huì)去喚醒生產(chǎn)者、消費(fèi)者,從而進(jìn)一步提升并發(fā)性能,適用于并發(fā)量大的場(chǎng)景
LinkedBlockingDeque
LinkedBlockingDeque實(shí)現(xiàn)與LinkedBlockQueue類似,在LinkedBlockQueue的基礎(chǔ)上支持從隊(duì)頭、隊(duì)尾進(jìn)行添加、刪除的操作
它是一個(gè)雙向鏈表,帶有一系列First、Last的方法,比如:offerLast、pollFirst
由于LinkedBlockingDeque雙向,常用其來實(shí)現(xiàn)工作竊取算法,從而減少線程的競(jìng)爭(zhēng)
什么是工作竊取算法?
比如多線程處理多個(gè)阻塞隊(duì)列的任務(wù)(一一對(duì)應(yīng)),每個(gè)線程從隊(duì)頭獲取任務(wù)處理,當(dāng)A線程處理完它負(fù)責(zé)的阻塞隊(duì)列所有任務(wù)時(shí),它再?gòu)年?duì)尾竊取其他阻塞隊(duì)列的任務(wù),這樣就不會(huì)發(fā)生競(jìng)爭(zhēng),除非隊(duì)列中只剩一個(gè)任務(wù),才會(huì)發(fā)生競(jìng)爭(zhēng)
ForkJoin框架就使用其來充當(dāng)阻塞隊(duì)列,我們后文再聊這個(gè)框架
PriorityBlockingQueue
PriorityBlockingQueue是優(yōu)先級(jí)排序的無界阻塞隊(duì)列,阻塞隊(duì)列按照優(yōu)先級(jí)進(jìn)行排序
使用堆排序,具體排序算法由Comparable或Comparator實(shí)現(xiàn)比較規(guī)則
- 默認(rèn):泛型中的對(duì)象需要實(shí)現(xiàn)
Comparable比較規(guī)則 ,根據(jù)compareTo方法規(guī)則排序 - 構(gòu)造器中指定比較器
Comparator根據(jù)比較器規(guī)則排序
@Test
public void testPriorityBlockingQeque() {
//默認(rèn)使用Integer實(shí)現(xiàn)Comparable的升序
PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>(6);
queue.offer(99);
queue.offer(1099);
queue.offer(299);
queue.offer(992);
queue.offer(99288);
queue.offer(995);
//99 299 992 995 1099 99288
while (!queue.isEmpty()){
System.out.print(" "+queue.poll());
}
System.out.println();
//指定Comparator 降序
queue = new PriorityBlockingQueue<>(6, (o1, o2) -> o2-o1);
queue.offer(99);
queue.offer(1099);
queue.offer(299);
queue.offer(992);
queue.offer(99288);
queue.offer(995);
//99288 1099 995 992 299 99
while (!queue.isEmpty()){
System.out.print(" "+queue.poll());
}
}
適用于需要根據(jù)優(yōu)先級(jí)排序處理的場(chǎng)景
DelayQueue
Delay是一個(gè)延時(shí)獲取元素的無界阻塞隊(duì)列, 延時(shí)最長(zhǎng)排在隊(duì)尾
Delay隊(duì)列元素實(shí)現(xiàn)Delayed接口通過getDelay獲取延時(shí)時(shí)間
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
}
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
DelayQueue應(yīng)用場(chǎng)景
- 緩存系統(tǒng)的設(shè)計(jì):DelayQueue存放緩存有效期,當(dāng)可以獲取到元素時(shí),說明緩存過期
- 定時(shí)任務(wù)調(diào)度: 將定時(shí)任務(wù)的時(shí)間設(shè)置為延時(shí)時(shí)間,一旦可以獲取到任務(wù)就開始執(zhí)行
以定時(shí)線程池ScheduledThreadPoolExecutor的定時(shí)任務(wù)ScheduledFutureTask為例,它實(shí)現(xiàn)Delayed獲取延遲執(zhí)行的時(shí)間
-
創(chuàng)建對(duì)象時(shí),初始化數(shù)據(jù)
ScheduledFutureTask(Runnable r, V result, long ns, long period) { super(r, result); //time記錄當(dāng)前對(duì)象延遲到什么時(shí)候可以使用,單位是納秒 this.time = ns; this.period = period; //sequenceNumber記錄元素在隊(duì)列中先后順序 sequencer原子自增 //AtomicLong sequencer = new AtomicLong(); this.sequenceNumber = sequencer.getAndIncrement(); } -
實(shí)現(xiàn)Delayed接口的getDelay方法
public long getDelay(TimeUnit unit) { return unit.convert(time - now(), NANOSECONDS); } -
Delay接口繼承了Comparable接口,目的是要實(shí)現(xiàn)compareTo方法來繼續(xù)排序
public int compareTo(Delayed other) { if (other == this) // compare zero if same object return 0; if (other instanceof ScheduledFutureTask) { ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other; long diff = time - x.time; if (diff < 0) return -1; else if (diff > 0) return 1; else if (sequenceNumber < x.sequenceNumber) return -1; else return 1; } long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; }
SynchronousQueue
SynchronousQueue是一個(gè)默認(rèn)下支持非公平不存儲(chǔ)元素的阻塞隊(duì)列
每個(gè)put操作要等待一個(gè)take操作,否則不能繼續(xù)添加元素會(huì)阻塞
使用公平鎖
@Test
public void testSynchronousQueue() throws InterruptedException {
final SynchronousQueue<Integer> queue = new SynchronousQueue(true);
new Thread(() -> {
try {
queue.put(1);
queue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "put12線程").start();
new Thread(() -> {
try {
queue.put(3);
queue.put(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "put34線程").start();
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());
}
//結(jié)果 因?yàn)槭褂霉芥i 1在2前,3在4前
//main拿出1
//main拿出3
//main拿出2
//main拿出4
SynchronousQueue隊(duì)列本身不存儲(chǔ)元素,負(fù)責(zé)把生產(chǎn)者的數(shù)據(jù)傳遞給消費(fèi)者,適合傳遞性的場(chǎng)景
在該場(chǎng)景下吞吐量會(huì)比ArrayBlockingQueue,LinkedBlockingQueue高
LinkedTransferQueue
LinkedTransferQueue是一個(gè)鏈表組成的無界阻塞隊(duì)列,擁有transfer()和tryTransfer()方法
transfer()
如果有消費(fèi)者在等待接收元素,transfer(e)會(huì)把元素e傳輸給消費(fèi)者
如果沒有消費(fèi)者在等待接收元素,transfer(e)會(huì)將元素e存放在隊(duì)尾,直到有消費(fèi)者獲取了才返回
@Test
public void testTransfer() throws InterruptedException {
LinkedTransferQueue queue = new LinkedTransferQueue();
new Thread(()->{
try {
//阻塞直到被獲取
queue.transfer(1);
//生產(chǎn)者放入的1被取走了
System.out.println(Thread.currentThread().getName()+"放入的1被取走了");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"生產(chǎn)者").start();
TimeUnit.SECONDS.sleep(3);
//main取出隊(duì)列中的元素
System.out.println(Thread.currentThread().getName()+"取出隊(duì)列中的元素");
queue.poll();
}
tryTransfer()無論消費(fèi)者是否消費(fèi)都直接返回
@Test
public void testTryTransfer() throws InterruptedException {
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
//false
System.out.println(queue.tryTransfer(1));
//null
System.out.println(queue.poll());
new Thread(()->{
try {
//消費(fèi)者取出2
System.out.println(Thread.currentThread().getName()+"取出"+queue.poll(2, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
},"消費(fèi)者").start();
TimeUnit.SECONDS.sleep(1);
//true
System.out.println(queue.tryTransfer(2));
}
tryTransfer(long,TimeUnit) 在超時(shí)時(shí)間內(nèi)消費(fèi)者消費(fèi)元素返回true,反之返回false
總結(jié)
ArrayBlockingQueue由環(huán)形數(shù)組實(shí)現(xiàn),固定容量無法擴(kuò)容,使用非公平的可重入鎖鎖、兩個(gè)等待隊(duì)列操作入隊(duì)、出隊(duì)操作,適合并發(fā)小的場(chǎng)景
LinkedBlockingQueue由單向鏈表實(shí)現(xiàn),默認(rèn)無界,使用兩個(gè)可重入鎖、兩個(gè)等待隊(duì)列進(jìn)行入隊(duì)、出隊(duì)操作,并在此期間可能喚醒生產(chǎn)者或消費(fèi)者線程,以此提高并發(fā)性能
LinkedBlockingDeque由雙向鏈表實(shí)現(xiàn),在LinkedBlockingQueue的基礎(chǔ)上,能夠在隊(duì)頭、隊(duì)尾都進(jìn)行添加、刪除操作,適用工作竊取算法1
PriorityBlockingQueue由堆排序?qū)崿F(xiàn)的優(yōu)先級(jí)隊(duì)列,具體排序算法由Comparable、Comparator來實(shí)現(xiàn),適用于需要根據(jù)優(yōu)先級(jí)排序處理任務(wù)的場(chǎng)景
DelayQueue 是一個(gè)延時(shí)隊(duì)列,隊(duì)列中存儲(chǔ)的元素需要實(shí)現(xiàn)Delayed接口來獲取延時(shí)時(shí)間,適用于緩存失效、定時(shí)任務(wù)的場(chǎng)景
SynchronousQueue不存儲(chǔ)元素,只將生產(chǎn)者生產(chǎn)的元素傳遞給消費(fèi)者, 適用于傳遞性的場(chǎng)景,比如不同線程間傳遞數(shù)據(jù)
LinkedTransgerQueue是傳輸形的阻塞隊(duì)列,適用于單個(gè)元素傳遞的場(chǎng)景
在使用無界的阻塞隊(duì)列時(shí),需要設(shè)置容量,避免存儲(chǔ)任務(wù)太多導(dǎo)致OOM
最后(不要白嫖,一鍵三連求求拉~)
本篇文章被收入專欄 由點(diǎn)到線,由線到面,深入淺出構(gòu)建Java并發(fā)編程知識(shí)體系,感興趣的同學(xué)可以持續(xù)關(guān)注喔
本篇文章筆記以及案例被收入 gitee-StudyJava、 github-StudyJava 感興趣的同學(xué)可以stat下持續(xù)關(guān)注喔~
案例地址:
Gitee-JavaConcurrentProgramming/src/main/java/E_BlockQueue
Github-JavaConcurrentProgramming/src/main/java/E_BlockQueue
有什么問題可以在評(píng)論區(qū)交流,如果覺得菜菜寫的不錯(cuò),可以點(diǎn)贊、關(guān)注、收藏支持一下~
關(guān)注菜菜,分享更多干貨,公眾號(hào):菜菜的后端私房菜
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!