BlockingQueue简介
BlockingQueue 继承了 Queue 接口,是队列的一种。Queue 和 BlockingQueue 都是在 Java 5
中加入的。阻塞队列(BlockingQueue)是一个在队列基础上又支持了两个附加操作的队列,两个附加操作(实现阻塞功能):
支持阻塞的插入方法put: 队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法take: 队列空时,获取元素的线程会等待队列变为非空
BlockingQueue常用方法示例
当队列满了无法添加元素,或者是队列空了无法移除元素时:
1. 抛出异常:add、remove、element
2. 返回结果但不抛出异常:offer、poll、peek
3. 阻塞:put、take
BlockingQueue应用场景
BlockingQueue 是线程安全的,我们在很多场景下都可以利用线程安全的队列来优雅地解决
我们业务自身的线程安全问题。
比如说,使用生产者/消费者模式的时候,我们生产者只需要往队列里添加元素,而消费者只需要从队列里取出它们就可以了。常见于类似于MQ之类的消息组件中。
因为阻塞队列是线程安全的,所以生产者和消费者都可以是多线程的,不会发生线程安全问
题。生产者/消费者直接使用线程安全的队列就可以,而不需要自己去考虑更多的线程安全问题。
这也就意味着,考虑锁等线程安全问题的重任从“你”转移到了“队列”上,降低了我们开发的
难度和工作量。
常见阻塞队列
BlockingQueue 接口的实现类都被放在了 juc 包中,它们的区别主要体现在存储结构上或对元素
操作上的不同,但是对于take与put操作的原理,却是类似的。
- ArrayBlockingQueue 基于数组结构实现的一个有界阻塞队列
- LinkedBlockingQueue 基于链表结构实现的一个无界阻塞队列,也可指定容量
- PriorityBlockingQueue 支持按优先级排序的无界阻塞队列(初始有界,可不断扩容)
- DelayQueue 基于PriorityBlockingQueue实现的无界阻塞队列
- SynchronousQueue 不存储元素的阻塞队列
- LinkedTransferQueue 基于链表结构实现的一个无界阻塞队列
- LinkedBlockingDeque 基于链表结构实现的一个双端阻塞队列
ArrayBlockingQueue
ArrayBlockingQueue是最典型的有界阻塞队列,其内部是用数组存储元素的,初始化时需要
指定容量大小,利用 ReentrantLock 实现线程安全。
在生产者-消费者模型中使用时,如果生产速度和消费速度基本匹配的情况下,使用
ArrayBlockingQueue是个不错选择;当如果生产速度远远大于消费速度,则会导致队列填满,大
量生产线程被阻塞。
使用独占锁ReentrantLock实现线程安全,入队和出队操作使用同一个锁对象,也就是只能有
一个线程可以进行入队或者出队操作;这也就意味着生产者和消费者无法并行操作,在高并发场
景下会成为性能瓶颈。
LinkedBlockingQueue
LinkedBlockingQueue是一个基于链表实现的阻塞队列,默认情况下,该阻塞队列的大小为
Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,
代表它几乎没有界限,队列可以随着元素的添加而动态增长,但是如果没有剩余内存,则队列
将抛出OOM错误。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的
时候建议手动传一个队列的大小。
LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。
LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都
有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。
SynchronousQueue
SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作
put必须等待消费者的移除操作take。
应用场景
SynchronousQueue非常适合传递性场景做交换工作,生产者的线程和消费者的线程同步传
递某些信息、事件或者任务。
SynchronousQueue的一个使用场景是在线程池里。如果我们不确定来自生产者请求数量,
但是这些请求需要很快的处理掉,那么配合SynchronousQueue为每个生产者请求分配一个消费
线程是处理效率最高的办法。Executors.newCachedThreadPool()就使用了
SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则
会重复使用,线程空闲了60秒后会被回收。
PriorityBlockingQueue
PriorityBlockingQueue是一个无界的基于数组的优先级阻塞队列,数组的默认长度是
11,虽然指定了数组的长度,但是可以无限的扩充,直到资源消耗尽为止,每次出队都返
回优先级别最高的或者最低的元素。默认情况下元素采用自然顺序升序排序,当然我们也可以
通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能
保证同优先级元素的顺序。
优先级队列PriorityQueue: 队列中每个元素都有一个优先级,出队的时候,优先级最高的
先出。
demo样例
package com.test.jucdemo.blokingqueue;
import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.PriorityBlockingQueue;
/**
* @author
*/
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
//创建优先级阻塞队列 Comparator为null,自然排序
PriorityBlockingQueue<Integer> queue=new PriorityBlockingQueue<Integer>(5);
// 自定义Comparator
// PriorityBlockingQueue queue=new PriorityBlockingQueue<Integer>(
// 5, new Comparator<Integer>() {
// @Override
// public int compare(Integer o1, Integer o2) {
// return o2-o1;
// }
// });
Random random = new Random();
System.out.println("put:");
for (int i = 0; i < 5; i++) {
int j = random.nextInt(100);
System.out.print(j+" ");
queue.put(j);
}
System.out.println("\ntake:");
for (int i = 0; i < 5; i++) {
System.out.print(queue.take()+" ");
}
}
}
应用场景
电商抢购活动,会员级别高的用户优先抢购到商品
银行办理业务,vip客户插队
DelayQueue
DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储
元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元
素,只有在延迟期满时才能从队列中提取元素。延迟队列的特点是:不是先进先出,而是会按照
延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。
它是无界队列,放入的元素必须实现 Delayed 接口,而 Delayed 接口又继承了
Comparable 接口,所以自然就拥有了比较和排序的能力,代码如下:
使用场景
商城订单超时关闭、异步短信通知功能、关闭空链接、缓存失效、任务超时处理。
package com.test.jucdemo.blokingqueue.delayqueue;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 模拟订单支付,创建订单
*/
public class OrderPay {
static String[] str = new String[]{ "成功", "支付中", "订单初始化" };
public static String getTime(long time) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(time);
String currentTime = formatter.format(date);
return currentTime;
}
public static void main(String[] args) throws InterruptedException {
OrderOverTimeClose.getInstance().init();
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20; i++) {
Runnable run = new Runnable() {
@Override
public void run() {
// 创建初始订单
long createTime = System.currentTimeMillis();
String currentTime = getTime(createTime);
//设置订单过期时间
String overTime = getTime(createTime + 10000);// 10s后超时
String orderNo = String.valueOf(new Random().nextLong());
OrderInfo order = new OrderInfo();
order.setOrderNo(orderNo);
order.setExpTime(overTime);
int random_index = (int) (Math.random() * str.length);
//设置订单状态
order.setStatus(str[random_index]);
order.setCreateTime(currentTime);
//插入订单到延时队列中 注意并发场景下使用单例模式,避免重复创建对象;
OrderOverTimeClose.getInstance().orderPutQueue(order, currentTime, overTime);
}
};
service.execute(run);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.test.jucdemo.blokingqueue.delayqueue;
import java.util.Objects;
import java.util.concurrent.DelayQueue;
/**
* 使用延时队列DelayQueue实现订单超时关闭
* 后台守护线程不断的执行检测工作
*
*/
public class OrderOverTimeClose {
private volatile static OrderOverTimeClose oderOverTimeClose = null;
private OrderOverTimeClose() {
}
/**
* 单例模式,双检查锁模式,在并发环境下对象只被初始化一次
*/
public static OrderOverTimeClose getInstance() {
if (oderOverTimeClose == null) {
synchronized (OrderOverTimeClose.class) {
if (oderOverTimeClose == null) {
oderOverTimeClose = new OrderOverTimeClose();
}
}
}
return oderOverTimeClose;
}
/**
* 守护线程
*/
private Thread mainThread;
/**
* 启动方法
*/
public void init() {
mainThread = new Thread(() -> execute());
mainThread.setDaemon(true);
mainThread.setName("守护线程-->");
mainThread.start();
}
/**
* 创建空延时队列
*/
private DelayQueue<OrderInfo> queue = new DelayQueue<OrderInfo>();
/**
* 读取延时队列,关闭超时订单
*/
private void execute() {
while (true) {
try {
if (queue.size() > 0) {
//从队列里获取超时的订单
OrderInfo orderInfo = queue.take();
// 检查订单状态,是否已经成功,成功则将订单从队列中删除。
if (Objects.equals(orderInfo.getStatus(), "成功")) {
//TODO 支付成功的订单处理逻辑
System.out.println("线程:" + Thread.currentThread().getName() + ",订单号:"
+ orderInfo.getOrderNo() + ",订单状态:"
+ orderInfo.getStatus() + ",订单创建时间:"
+ orderInfo.getCreateTime()
+ ",订单超时时间:" + orderInfo.getExpTime() + ",当前时间:"
+ OrderPay.getTime(System.currentTimeMillis()));
Thread.sleep(2000);
} else {
// TODO 超时未支付订单处理逻辑
System.out.println("线程:" + Thread.currentThread().getName() + ",订单号:"
+ orderInfo.getOrderNo() + ",变更订单状态为:超时关闭"
+ ",订单创建时间:"
+ orderInfo.getCreateTime()
+ ",订单超时时间:" + orderInfo.getExpTime() + ",当前时间:"
+ OrderPay.getTime(System.currentTimeMillis()));
Thread.sleep(2000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 插入订单到延时队列中
*/
public void orderPutQueue(OrderInfo orderInfo, String createTime,
String overTime) {
System.out.println("订单号:" + orderInfo.getOrderNo()
+",订单状态:"+ orderInfo.getStatus()
+",订单创建时间:"+ createTime
+ ",订单过期时间:" + overTime);
queue.add(orderInfo);
}
}
package com.test.jucdemo.blokingqueue.delayqueue;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 订单对象
*/
public class OrderInfo implements Delayed,Serializable {
private static final long serialVersionUID = 1L;
private String orderNo;// 订单号
private String status;// 订单状态
private String expTime;// 订单过期时间
private String createTime;//订单创建时间
/**
* 用于延时队列内部比较排序:当前订单的过期时间 与 队列中对象的过期时间 比较
*/
@Override
public int compareTo(Delayed o) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long nowThreadtime = 0;
long queueThreadtime = 0;
try {
nowThreadtime = formatter.parse(this.expTime).getTime();
queueThreadtime = formatter.parse(((OrderInfo) o).expTime).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return Long.valueOf(nowThreadtime).compareTo(Long.valueOf(queueThreadtime));
}
/**
* 时间单位:秒
* 延迟关闭时间 = 过期时间 - 当前时间
*/
@Override
public long getDelay(TimeUnit unit) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long time = 0;
try {
time = formatter.parse(this.expTime).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return time - System.currentTimeMillis();
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public String getExpTime() {
return expTime;
}
public void setExpTime(String overTime) {
this.expTime = overTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
}