文章目录
阻塞队列分析
解决问题
-
问题
在concurrent包发布之前,程序员需要自己去控制线程的阻塞和唤醒,同时兼顾效率和线程安全,这给程序带来了不小的复杂度
-
解决
使用阻塞队列,BlockingQueue,减少代码复杂程度
优势劣势
-
优势
不需要关心什么时候阻塞线程,什么时候唤醒线程,BlockingQueue解决了这些问题
-
劣势
没有明显的缺点,根据不同的场景选用不同的阻塞队列即可
适用场景
- 生产者-消费者模型
- 线程池
- 消息中间件
组成部分
阻塞队列
- 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞
- 当阻塞队列是满时,往队列里添加元素的操作将会被阻塞
类型
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:由链表结构组成的有界(初始容量为Integer.MAX_VALUE)阻塞队列
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列
- SynchronousQueue:不存储元素的队列,也即单个元素队列
- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
核心方法
Throws Exception | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
Insert | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
Remove | remove(o) | poll() | take() | poll(timeout, timeunit) |
Examine | element() | peek() |
- Throws Exception: 如果尝试的操作不可能立即发生,则抛出一个异常。
- 特殊值:如果尝试的操作不能立即执行,则会返回一个特殊值(通常为true / false)。
- 阻塞:如果尝试的操作不可能立即执行,那么该方法将阻塞。
- 超时:如果尝试的操作不可能立即执行,则该方法调用将阻塞,但不会超过给定的超时。
返回一个特殊值,告诉操作是否成功(通常为true / false)。
无法将null插入到BlockingQueue中。如果尝试插入null,则BlockingQueue将引发NullPointerException。
底层原理
前面谈到了非阻塞队列和阻塞队列中常用的方法,下面来探讨阻塞队列的实现原理,本文以ArrayBlockingQueue为例,其他阻塞队列实现原理可能和ArrayBlockingQueue有一些差别,但是大体思路应该类似,有兴趣的朋友可自行查看其他阻塞队列的实现源码。
首先看一下ArrayBlockingQueue类中的几个成员变量:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
/** The queued items */
private final E[] items;
/** items index for next take, poll or remove */
private int takeIndex;
/** items index for next put, offer, or add. */
private int putIndex;
/** Number of items in the queue */
private int count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
private final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
}
可以看出,ArrayBlockingQueue中用来存储元素的实际上是一个数组,takeIndex和putIndex分别表示队首元素和队尾元素的下标,count表示队列中元素的个数。
lock是一个可重入锁,notEmpty和notFull是等待条件。
下面看一下ArrayBlockingQueue的构造器,构造器有三个重载版本:
public ArrayBlockingQueue(int capacity) {
}
public ArrayBlockingQueue(int capacity, boolean fair) {
}
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
}
第一个构造器只有一个参数用来指定容量,第二个构造器可以指定容量和公平性,第三个构造器可以指定容量、公平性以及用另外一个集合进行初始化。
然后看它的两个关键方法的实现:put()和take():
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
} finally {
lock.unlock();
}
}
从put方法的实现可以看出,它先获取了锁,并且获取的是可中断锁,然后判断当前元素个数是否等于数组的长度,如果相等,则调用notFull.await()进行等待,如果捕获到中断异常,则唤醒线程并抛出异常。
当被其他线程唤醒时,通过insert(e)方法插入元素,最后解锁。
我们看一下insert方法的实现:
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
它是一个private方法,插入成功后,通过notEmpty唤醒正在等待取元素的线程。
下面是take()方法的实现:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == 0)
notEmpty.await();
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
throw ie;
}
E x = extract();
return x;
} finally {
lock.unlock();
}
}
跟put方法实现很类似,只不过put方法等待的是notFull信号,而take方法等待的是notEmpty信号。在take方法中,如果可以取元素,则通过extract方法取得元素,下面是extract方法的实现:
private E extract() {
final E[] items = this.items;
E x = items[takeIndex];
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
跟insert方法也很类似。
其实从这里大家应该明白了阻塞队列的实现原理,事实它和我们用Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者的思路类似,只不过它把这些工作一起集成到了阻塞队列中实现。
相似对比
非阻塞队列实现生产者-消费者模式(Object)
使用Object的wait/notify的消息通知机制
package top.ygy.thread.productor;
import java.util.ArrayList;
import java.util.List;
public class ConditionChange {
private static List<String> lockObject = new ArrayList();
public static void main(String[] args) {
Consumer consumer1 = new Consumer(lockObject);
Consumer consumer2 = new Consumer(lockObject);
Productor productor = new Productor(lockObject);
consumer1.start();
consumer2.start();
productor.start();
}
static class Consumer extends Thread {
private List<String> lock;
public Consumer(List lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
try {
// 这里使用if的话,就会存在wait条件变化造成程序错误的问题
while (lock.isEmpty()) {
System.out.println(
Thread.currentThread().getName() + " list为空");
System.out.println(
Thread.currentThread().getName() + " 调用wait方法");
lock.wait();
System.out.println(Thread.currentThread().getName()
+ " wait方法结束");
}
String element = lock.remove(0);
System.out.println(Thread.currentThread().getName()
+ " 取出第一个元素为:" + element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Productor extends Thread {
private List<String> lock;
public Productor(List lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out
.println(Thread.currentThread().getName() + " 开始添加元素");
lock.add(Thread.currentThread().getName());
lock.notifyAll();
}
}
}
}
非阻塞队列实现生产者-消费者模式(Lock)
使用Lock的Condition的await/signal的消息通知机制
package top.ygy.thread.productor;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description: TODO(这里用一句话描述这个类的作用)
* @author yangguangyuan
* @date 2019年6月20日 题目:一个初始值为0的变量,两个线程对其交替操作,一个加1一个减1,来5轮
*
* 1. 线程 操作 资源类
* 2. 判断 干活 通知
*/
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for(int i=1;i<=5;i++) {
shareData.increment();
}
},"AA").start();
new Thread(() -> {
for(int i=1;i<=5;i++) {
shareData.decrement();
}
},"BB").start();
new Thread(() -> {
for(int i=1;i<=5;i++) {
shareData.increment();
}
},"CC").start();
new Thread(() -> {
for(int i=1;i<=5;i++) {
shareData.decrement();
}
},"DD").start();
}
}
class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
// 1.判断
while (number != 0) {
condition.await();
}
number++;
System.out
.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
// 1.判断
while (number == 0) {
condition.await();
}
number--;
System.out
.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
阻塞队列实现的生产者-消费者模式(BlockingQueue)
使用BlockingQueue实现
package top.ygy.thread.productor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description: TODO(阻塞队列)
* @author yangguangyuan
* @date 2019年6月21日
*
* volatile/CAS/AtomicInteger/BlockingQueue/线程交互/原子引用
*/
public class ProdConsumer_BlockQueue {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 生产线程启动");
try {
myResource.myProd();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 消费线程启动");
try {
myResource.myConsumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Consumer").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myResource.stop();
}
}
class MyResource {
// 默认开启进行生产+消费
private volatile boolean FLAG = true;
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws InterruptedException {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列"
+ data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列"
+ data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println();
System.out.println();
System.out.println("生产者停止,FLAG=false,生产动作结束");
}
public void myConsumer() throws InterruptedException {
String result = null;
while (FLAG) {
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null == result || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(
Thread.currentThread().getName() + "\t 消费者停止,超时2s,消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费队列"
+ result + "成功");
}
System.out.println("消费者停止,FLAG=false,消费动作结束");
}
public void stop() {
FLAG = false;
}
}
参考: