今天来看一下 JDK 中的阻塞队列,这个容器也很重要,因为我们做消息中间件必须用到。
什么是阻塞队列?就是给普通的队列增加阻塞操作。
分类
JAVA 里提供了 7 个阻塞队列,分别是:
- ArrayBlockingQueue:一个由数据结构组成的有界阻塞队列
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列
- PriortyBlockingQueue:一个支持优先级排序的无界阻塞队列
- DelayQueue:一个使用优先级队列实现的无界阻塞队列
- SynchronousQueue:一个不存储元素的阻塞队列
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
ArrayBlockingQueue
ArrayBlockingQueue是用一个数组实现的有界阻塞队列,是一个FIFO队列。默认是非公平的(不按照线程阻塞的顺序插入或者弹出)。
LinkedBlockingQueue
LinkedBlockingQueue一个由链表结构组成的有界阻塞队列,此队列的默认和最大长度为Integer.MAX_VALUE,也是一个FIFO队列。
PriortyBlockingQueue
PriortyBlockingQueue是一个支持优先级排序的无界阻塞队列,默认按照元素自然顺序升序排列。也可以自定类实现compareTo()来指定排序规则。或者初始化时,指定构造参数Comparator来对元素进行排序。需要注意的是,如果两个元素优先级相同,不能保证它们的顺序。
DelayQueue
DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriortyBlockingQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才可以提取元素。
介绍一下DelayQueue的用途:
缓存系统的设计
可以用
DelayQueue来保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦从DelayQueue中获取到数据,则说明这个缓存有效期到了。定时任务调度
使用
DelayQueue保存当天将会执行的任务和时间,一旦从DelayQueue获取到任务就开始执行。比如TimerQueue就是基于DelayQueue实现的。
SynchronousQueue
SynchronousQueue是一个不存储元素的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素。默认是非公平的。这个队列比较特殊,可以认为是一个传球手,负责把生产者处理的数据直接传递给消费者线程。队列本身不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于ArrayBlockingQueue和LinkedBlockingQueue。在cacheThreadPool中就是用了这种队列。
LinkedTransferQueue
LinkedTransferQueue是一个由链表结构组成的无界阻塞队列。相对于其他队列,它多了两个方法:transfer()和tryTransfer()。如果有消费者需要拿元素,transfer()方法可以把生产者传入的元素立即传给消费者;如果没有,则会把元素放在tail节点,等到该元素被消费了才返回,也就是说transfer()必须等到消费者消费了才返回,tryTransfer()顾名思义,尝试传输,不用等待如果传输失败直接返回 false。
LinkedBlockingDeque
LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,所谓双向就是可以队尾和队头插入和弹出元素。在初始化时可以设置容量,防止过度膨胀。双向阻塞队列可以用在工作窃取中。
原理
其实知道多线程几种阻塞机制,很容易就能实现阻塞的功能,比如wait-notify,reentrantLock.condition和LockSupport.park(this),其实阻塞队列也就是这几种模式实现的。
常用操作
生产者-消费者模式
使用BlockingQueue实现生产者-消费者模式进行解耦。
生产者,发出求救信号:
public class SOSProducer implements Runnable {
private BlockingQueue<SOSData> queue;
public SOSProducer(BlockingQueue<SOSData> queue) {
this.queue = queue;
}
@Override
public void run() {
for (;;) {
try {
long waiting = ThreadLocalRandom.current().nextLong(5);
TimeUnit.SECONDS.sleep(waiting);
queue.put(new SOSData(System.currentTimeMillis()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者,接收求救信号并打印:
public class SOSConsumer implements Runnable {
private BlockingQueue<SOSData> queue;
public SOSConsumer(BlockingQueue<SOSData> queue) {
this.queue = queue;
}
@Override
public void run() {
for (;;) {
try {
SOSData data = queue.take();
System.out.println(Thread.currentThread().getName() + " 接收到求救信号:" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
客户端:
private static final int PRODUCER_THREAD_NUM = 10;
private static final int CONSUMER_THREAD_NUM = 2;
public static void main(String[] args) {
BlockingQueue<SOSData> queue = new LinkedBlockingQueue<SOSData>(100);
ExecutorService producerPool = Executors.newFixedThreadPool(PRODUCER_THREAD_NUM);
ExecutorService consumerPool = Executors.newFixedThreadPool(CONSUMER_THREAD_NUM);
for (int i = 0; i < PRODUCER_THREAD_NUM; i++) {
producerPool.execute(new SOSProducer(queue));
}
for (int i = 0; i < CONSUMER_THREAD_NUM; i++) {
consumerPool.execute(new SOSConsumer(queue));
}
}
console output:
pool-2-thread-2 接收到求救信号:SOSData [sosTime=1555426748063]
pool-2-thread-1 接收到求救信号:SOSData [sosTime=1555426748063]
pool-2-thread-2 接收到求救信号:SOSData [sosTime=1555426748064]
pool-2-thread-1 接收到求救信号:SOSData [sosTime=1555426748064]
pool-2-thread-2 接收到求救信号:SOSData [sosTime=1555426748063]
pool-2-thread-1 接收到求救信号:SOSData [sosTime=1555426749063]
pool-2-thread-2 接收到求救信号:SOSData [sosTime=1555426750063]
pool-2-thread-1 接收到求救信号:SOSData [sosTime=1555426750063]
pool-2-thread-2 接收到求救信号:SOSData [sosTime=1555426750063]
延迟订单
使用delayQueue实现,其实这种基于内存的不太可靠,这里演示下用法,方便写 demo 时使用:
/**
* 延迟元素
*
*/
public class DelayItem<T> implements Delayed {
/**
* 到期时间(执行时间)单位纳秒
*/
private long executeTime;
/**
* 数据
*/
private T data;
// time是过期时长,也就是延迟多少毫秒 5*1000即为5秒
public DelayItem(long delayTime, T data) {
// 将传入的时长转为超时的时刻
this.executeTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime();
this.data = data;
}
// 按照剩余时间排序
@Override
public int compareTo(Delayed o) {
long d = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return (d == 0) ? 0 : (d > 0 ? 1 : -1);
}
// 该方法返回还需要延时多少时间,单位为纳秒,所以设计的时候最好使用纳秒
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
}
public long getExecuteTime() {
return executeTime;
}
public T getData() {
return data;
}
}
总结
本文演示了下基本的阻塞队列,并没有对原理进行分析,其实实现原理并不复杂,就是基于显示锁的等待通知。
EOF