Queue队列
队列FIFO, 先进先出。
Queue接口抽象出了以下几个方法, 我们可以实现Queue接口,定制贴合自己业务的队列
boolean add(E e) | 添加元素 | 容量不足抛异常 |
boolean offer(E e) | 添加元素 | 容量不足返回false |
E remove() | 删除头元素 | 队列为空抛异常 |
E poll() | 删除头元素 | 队列为空返回null |
E element() | 检索但并不删除头元素 | 队列为空抛异常 |
E peek() | 检索但并不删除头元素 | 队列为空返回null |
阻塞队列
队列 | 描述 |
ArrayBlockingQueue | 基于数组结构实现的一个有界阻塞队列 |
LinkedBlockingQueue | 基于链表结构实现的一个无界阻塞队列,指定容量为有界阻塞队列 |
PriorityBlockingQueue | 支持按优先级排序的无界阻塞队列 |
DelayQueue | 基于优先级队列(PriorityBlockingQueue)实现的无界阻塞队列 |
SynchronousQueue | 不存储元素的阻塞队列 |
LinkedTransferQueue | 基于链表结构实现的一个无界阻塞队列 |
LinkedBlockingDeque | 基于链表结构实现的一个双端阻塞队列 |
ArrayBlockingQueue
基于环形数组实现的有界阻塞队列,利用ReentrantLock实现线程安全, 入队和出队公用一把锁, 可能导致并发性能问题
结构:
- 有界环形数组(初始化时指定数组容量, 不能扩容)
- 一把ReentrantLock锁
- 两个Condition条件(notEmpty, notFull)
- 两个指针标识入队和出队元素在数组下标
LinkedBlockingQueue
基于链表实现的阻塞队列, 不指定容量时默认Integer.MAX_VALUE(2的31次方-1), 因为容量很大, 也常被称作无界阻塞队列.在生产消费场景中, 如果生产速度大于消费速度, 可能导致OOM. 建议使用时, 测试出生产消费速度, 设置队列容量, 防止OOM.
结构:
- 链表, 有头节点和尾节点
- 两把ReentrantLock锁, 入队锁putLock和出队锁takeLock
- 两个Condition条件(notEmpty, notFull)
PriorityBlockingQueue
基于PriorityQueue实现的阻塞队列, 有初始容量11, 会自动扩容(50%), 入队和出队共用一把锁
示例: vip用户插队
@Slf4j
public class PriorityDemo {
private static PriorityBlockingQueue<User> blockingQueue = new PriorityBlockingQueue<>();
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
produce(new User("小" + i, i % 5 == 0));
}
blockingQueue.offer(new User(null));
new Thread(PriorityDemo::comsume).start();
}
private static void produce(User user) {
blockingQueue.offer(user);
log.info("开始排队 " + user.getDesc());
}
private static void comsume() {
User user;
try {
while ((user = blockingQueue.take()).getName() != null) {
log.info("开始叫号, " + user.getDesc() + " 请进...");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Data
static class User implements Comparable<User> {
private String name;
private boolean isVip;
private String getDesc() {
if (name == null) return null;
return (isVip ? "VIP" : "平民") + "[" + name + "]";
}
public User(String name) {
this.name = name;
}
public User(String name, boolean isVip) {
this.name = name;
this.isVip = isVip;
}
@Override
public int compareTo(User o) {
if (o.getName() == null) {
return -1;
}
if (name == null) {
return 1;
}
return o.isVip ? 1 : -1;
}
}
}
执行结果:
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小1]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小2]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小3]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小4]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 VIP[小5]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小6]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小7]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小8]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 平民[小9]
[main][INFO][23/10/24]-org.example.queue.PriorityDemo.produce(PriorityDemo.java:31):开始排队 VIP[小10]
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, VIP[小5] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, VIP[小10] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小3] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小4] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小8] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小6] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小2] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小9] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小1] 请进...
[Thread-0][INFO][23/10/24]-org.example.queue.PriorityDemo.comsume(PriorityDemo.java:38):开始叫号, 平民[小7] 请进...
如何选择队列
1. 功能. 若业务需要延时, 优先级. 排序, 可考虑用PriorityBlockingQueue之类的阻塞队列
2. 容量. 判断当前是否需要考虑内存成本, 来判断选用有界/无界/无容量队列(SynchronousQueue)
3. 能否扩容. 若不能判断高峰业务具体容量大小, 应支持动态扩容, 不能用ArrayBlockingQueue
4. 内存结构. ArrayBlockingQueue内部结构是数组, 需要一块连续内存空间; LinkedBlockingQueue是一个链表, 带有Node结构, ArrayBlockingQueue没有链表Node节点, 空间利用率更高
5. 性能. ArrayBlockingQueue和PriorityBlockingQueue入队出队共用一把锁, LinkedBlockingQueue有两把锁, LinkedBlockingQueue并发性能更高.
6. 是否直接传递. 如果直接传递用SynchronousQueue, 不需要额外空间把任务放到队列中, 节省内存
线程池中的队列
- FixedThreadPool: LinkedBlockingQueue
- CachedThreadPool: SynchronousQueue
- ScheduledThreadPool: DelayQueue