首先自己手动实现一个简单的阻塞队列,然后使用自己写的阻塞队列实现生产者消费者模式,我们需要了解什么是阻塞队列,如下:
阻塞队列特性:
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列实现步骤:
- 实现一个队列
- 线程安全
- 阻塞功能
具体代码:
//阻塞队列/消息队列的简单实现
public class MyBlockQueue {
int head = 0;
int tail = 0;
int[] arr = null;
int size = 0;
int len;
public MyBlockQueue(int len) {
this.len = len;
arr = new int[len];
}
public synchronized void put(int val) throws InterruptedException {
//如果满了就阻塞
while (size == len) {
this.wait();
}
//没满就插入数据
arr[tail] = val;
tail++;
size++;
if (tail >= len) {
tail = 0;
}
//唤醒其他线程
this.notify();
}
public synchronized int take() throws InterruptedException {
//如果为空 则阻塞
while (size == 0) {
this.wait();
}
//不为空则取数据
int res = arr[head];
head++;
size--;
if (head == len) {
head = 0;`在这里插入代码片`
}
//唤醒其他线程 并返回结果
this.notify();
return res;
}
}
接下来用阻塞队列实现生产者消费者模型。
具体代码:
//生产者消费者模型
public class Demo_3 {
public static void main(String[] args) {
MyBlockQueue blockQueue = new MyBlockQueue(6);
//生产者产生一百个数据
Thread producer = new Thread(() -> {
try {
int num = 0;
for (int i = 0; i < 100; i++) {
num++;
System.out.println("生产了数据" + num);
blockQueue.put(num);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//消费者不断消费数据
Thread consumer = new Thread(() -> {
try {
while (true) {
int num = blockQueue.take();
System.out.println("消费了数据" + num);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
遇到的问题解释:
为什么阻塞队列的阻塞功能实现那里判断使用的是while而不是if?
//如果满了就阻塞
while (size == len) {
this.wait();
}
//如果为空 则阻塞
while (size == 0) {
this.wait();
}
解答: 首先wait会让线程进入WAITING状态,且被唤醒之后会从wait处继续执行代码,wait后会释放所拥有的锁。 若有一个生产者,两个(多个)消费者,如果生产者生产了一个数据。然后调用notifyAll(),唤醒了两个消费者,若我们使用的是if,那么程序接着从wait处执行,那么两个消费者都会从阻塞队列中取数据?但是实际上只有一个数据,这就产生了所谓的虚假唤醒。
如果我们使用的是while,那么程序接着从wait处执行,会重新while判断一下是否有数据,第一个获得锁的对象判断为有数据,取出,第二个获得锁的对象接着判断,发现没数据了,继续进入阻塞状态。