阻塞队列
BlockingQueue 是一个接口,不能直接用,要用它的实现类。它的实现基于 ReentrantLock。
阻塞队列的特点
- 当队列为空。从队列中获取元素的操作会被阻塞,直到队列中有新的元素可用
- 当队列已满时,像队列中添加元素的操作会被阻塞,直到队列中有空位可用
阻塞队列应用
- 生产者-消费者模型。原因::不需要额外的实现线程同步和唤醒
- 线程池的任务队列:阻塞队列可以用来作为线程池的任务队列
- 线程同步:例如多个线程共享一个阻塞队列,当一个线程需要获取某个元素时,如果队列为空,该线程就会被阻塞,直到其他线程将元素添加到队列中。
- 数据的分发和收集。可用于数据从一个线程分发到其他线程、或者从多个线程收集数据
阻塞队列实现类
1. ArrayBlockingQueue:
特征:基于数组实现,队列容量固定。存/取数据的操作公用同一把锁(默认非公平锁)。无法实现真正意义上存/取操作的并行执行。
分析:由于基于数组,容量固定所以不容易出现内存占用率过高,但是如果容量太小,取数据比存数据的速度慢,那么会造成过多的线程进入阻塞(也可以使用offer()方法达到不阻塞线程), 此外由于存取共用一把锁,所以有高并发和吞吐量的要求情况下,我们也不建议使用ArrayBlockingQueue。
使用场景:更适合次级业务中
2. LinkedBlockingQueue:
特征:LinkedBlockingQueue 基于链表实现,队列容量默认 Integer.MAX_VALUE。存/取数据的操作分别拥有独立的锁,可实现存/取并行执行。
分析:
1、基于链表,数据新增和移除速度块,但是每次存/取数据都会有 Node 对象的新建和移除,所以存在由于 GC 影响性能的可能。
2、默认容量非常大,所以存储数据的线程基本不会阻塞,但是如果消费速度过低,内存占用可能会飙升。
3、读/取操作锁分离,所以适合并发和吞吐量要求的项目中
使用场景:在项目中的一些核心业务且生产和消费相似的场景中。
如:订单完成的邮件/短信提醒
3. PriorityBlockingQueue:
特征:基于数组实现。队列最大容量 Integer.MAX_VALUE - 8 (减 8 是因为数组的对象头)。根据传入的优先级进行排序,保证按优先级来消费。
分析:优先级阻塞队列中存在一次排序,根据优先级来将数据放入到头部或者尾部。排序带来的损耗因素,有二叉树最小堆排序算法来降低。
使用场景:在项目上有优先级的业务。
VIP 排队购票
4. DelayQueue:
特征:DelayQueue 延迟队列,基于优先级队列来实现。存储元素必须实现 Delayed 接口。该接口继承了 Comparable 接口。
分析:由于基于优先级队列实现。但是它比较的是时间,根据时间倒序或正序排列
使用场景:订单支付超时取消功能;网站刷题倒计时
5. SynchronousQueue:
特征:采用双栈双队列算法的无空间队列或栈。任何一个对 SynchronousQueue 些需要等到一个对 SynchronousQueue 的读操作,任何一个读操作需要等待一个写操作。没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者。
分析:相当于交换通道,不存储任何元素,提供者和消费者是需要组队完成工作,缺少一个将会阻塞队列,直到等到配对为止。
使用场景:线程池 newCachedThreadPool()
;轻量级别的任务转交:会话转交
源码展示
源码一、优先级队列
public class QueueTest {
static class Ticket implements Comparable<Ticket> {
private int level;
public Ticket(int level) {
this.level = level;
}
@Override
public int compareTo(Ticket o) {
//优先级高的返回-1
if (this.level > o.level)
return -1;
else
return 1;
}
}
public static void main(String[] args) {
//VIP客户、各大机场的VIP客户的优先登机,加速抢票
BlockingQueue<Ticket> queue1 = new PriorityBlockingQueue<>();
Ticket ticket = new Ticket(0);
Ticket ticket1 = new Ticket(1);
Ticket ticket2 = new Ticket(2);
Ticket ticket3 = new Ticket(-1);
queue1.add(ticket);
queue1.add(ticket1);
queue1.add(ticket2);
queue1.add(ticket3);
for (; ; ) {
try {
System.out.println(queue1.take().level);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
源码二、延迟队列
public class QueueTest {
static class Work implements Delayed {
//名称
private String name;
//时长
private long time;
public Work(String name, long time, TimeUnit unit) {
this.name = name;
this.time = System.currentTimeMillis() + (time > 0 ? unit.toMillis(time) : 0);
}
//时间
@Override
public long getDelay(TimeUnit unit) {
return time - System.currentTimeMillis();
}
@Override
public int compareTo(Delayed o) {
Work work = (Work) o;
long diff = this.time - work.time;
if (diff <= 0) {// 改成>=会造成问题
return -1;
} else {
return 1;
}
}
}
public static void main(String[] args) {
BlockingQueue<Work> queue3 = new DelayQueue<>();
try {
Work work = new Work("用户一", 25, TimeUnit.SECONDS);
Work work2 = new Work("用户二", 5, TimeUnit.SECONDS);
Work work3 = new Work("用户三", 15, TimeUnit.SECONDS);
queue3.add(work);
queue3.add(work2);
queue3.add(work3);
for (; ; ) {
Work work1 = queue3.take();
System.out.println(work1.name + "," + work1.time);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}