阻塞队列(BlockingQueue):
是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法
1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
阻塞队列的具体实现:
普通的队列是怎么实现的?
- 基于链表
- 基于数组 (今天我使用的)
代码实现:
static class BlockingQueue {
private int[] array = new int[1000];
private volatile int head = 0;
private volatile int tail = 0;
// head 和 tail 构成的是一个前闭后开区间.
// 当两者重合的时候, 可能是表示队列空, 也可能是表示队列满.
// 为了区分空还是满, 就需要额外引入一个 size 来表示.
private volatile int size = 0;
// 队列的基本操作
// 1. 入队列
// 2. 出队列
// 3. 取队首元素
// 针对阻塞队列来说, 只提供前两个操作, 不支持取队首元素.
// 阻塞版本的入队列, 为了和之前的版本区分, 用了不同的名字.
public void put(int value) throws InterruptedException {
synchronized (this) {
if(size == array.length) {
wait();
}
// 把 value 放到队尾即可
array[tail] = value;
tail++;
if (tail == array.length) {
tail = 0;
}
size++;
notify();
}
}
// 阻塞版本的出队列, 用了不同的名字
public int take() throws InterruptedException {
int ret = -1;
synchronized (this) {
if (size == 0) {
this.wait();
}
ret = array[head];
head++;
if (head == array.length) {
head = 0;
}
size--;
notify();
}
return ret;
}
}
问题来了!
1.下面两个wait操作有没有可能同时被触发?
答案是不可能的,因为两个wait的触发条件一个是队列为满,一个是队列为空。
2.如果是多个线程wait,那么notify会唤醒哪个线程呢?
答案是随机的,因为这由操作系统调度器说了算。
3.如果把代码中的notify换成notifyAll是否可行?
答案:不行,会出现问题。
4.如果坚持改成notifyAll代码该如何修改?
把判断条件if改成while
测试代码:
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new BlockingQueue();
// 搞两个线程, 分别模拟生产者和消费者.
// 第一次, 让给消费者消费的快一些, 生产者生产的慢一些.
// 此时就预期看到, 消费者线程会阻塞等待. 每次有新生产的元素的时候, 消费者才能消费
// 第二次, 让消费者消费的慢一些, 生产者生产的快一些.
// 此时就预期看到, 生产者线程刚开始的时候会快速的往队列中插入元素, 插入满了之后就会阻塞等待.
// 随后消费者线程每次消费一个元素, 生产者才能生产新的元素.
Thread producer = new Thread(){
@Override
public void run() {
for(int i = 0; i<1000; i++) {
try {
blockingQueue.put(i);
System.out.println("生产元素:"+i);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
producer.start();
Thread consumer = new Thread() {
@Override
public void run() {
while (true) {
try {
int ret = blockingQueue.take();
System.out.println("消费元素: " + ret);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
consumer.start();
}
测试结果:
快慢由sleep控制
1.先让生产的快一点
2.让消费的快一点
总结:
阻塞队列是基于普通队列实现的,与多线程联系到一起。主要是对wait()与notify()操作的应用。
如果有错误希望大家多多指正。