阻塞队列
生产者 - 消费者模型
标准库中的阻塞队列
循环队列实现阻塞队列
实现生产者 - 消费者模型
阻塞队列是一个符合先进先出的队列, 相比于普通队列, 阻塞队列有一些其他方面的功能:
基于上述特性, 就可以实现"生产者-消费者模型"(这是日常开发中, 处理多线程问题的一个典型的方式)
有两个服务器A, B, 服务器A作为入口服务器, 用来接收用户发送的数据, 服务器B作为应用服务器, 来为A提供一些数据.
如果不使用生产者 - 消费者模型, 此时A和B的耦合性较强, 一旦服务器B出现故障, 很有可能导致服务器A也出现故障.
而如果使用生产者 - 消费者模型, 就可以降低A和B的耦合度.
为服务器A和B之间添加一条阻塞队列, 这样, A和B之间就不需要进行交互, 一旦一个服务器出现问题, 另一个也不会受到影响.
这就是生产者 - 消费者模型的优点一:能够让多个服务器程序之间更充分地解耦合.
对于这两个服务器, 如果某一时刻请求量暴涨(不可控), 此时A作为入口服务器, 并不需要进行过多的计算, 并不会受到太多的影响, 但B作为应用服务器, 需要进行大量的计算, 也需要更多的系统资源, 此时如果硬件资源不够, 很可能会导致服务器崩溃.
此时如果采用生产者 - 消费者模型, A的请求暴涨只会导致阻塞队列的请求量暴涨, 而阻塞队列只需要存储数据, 阻塞队列只需要继续按照原有的速度对B服务器发送请求, B也就不会收到影响了.
这就是生产者 - 消费者模型的优点二:能够对于请求进行"削峰填谷".
这里的阻塞队列的功能仅限于存储数据, 然而, 实际开发中用到的并不是阻塞队列, 而是消息队列, 消息队列会实现更多的功能, 例如, 数据持久化存储, 支持多个数据通道, 支持多节点容灾冗余备份, 支持管理面板, 方便配置参数…消息队列并不是这里的一个简单的数据结构, 而是一组服务器程序.
Java标准库中内置了阻塞队列, 当我们需要在程序中实现阻塞队列时, 直接调用即可.
public static void main(String[] args)throws InterruptedException {
//生成阻塞队列
BlockingQueue<String > queue = new LinkedBlockingQueue<>();
//入队列
queue.put("hello");
//出队列
System.out.println(queue.take());
}
//结果:
hello
先创建一个循环队列:
class MyBlockingQueue{
//数组实现循环队列
static int[] array = new int[100];
//队首
private int head;
//队尾
private int tail;
//队列长度
private int size;
//入队列
public void put(int value){
if(size == array.length){
return;
}
array[tail] = value;
++size;
if(++tail >= array.length){
tail = 0;
}
}
//出队列
public Integer take(){
if(size == 0){
return null;
}
int ret = array[head];
++head;
if(head >= array.length){
head = 0;
}
--size;
return ret;
}
}
创建好循环队列之后, 就要让这个队列支持线程安全, 保证在多线程下, 调用这里的put()和take()方法不出现问题, 那么就要对方法进行加锁.
可以看到, 这两个方法中到处都在对公共变量进行修改, 那我们直接对方法进行加锁即可:
//入队列
synchronized public void put(int value){
}
//出队列
synchronized public Integer take(){
}
保证了线程安全之后, 接下来就只需要实现阻塞效果了. 我们想要实现的阻塞效果是:队列为满时入队列, 产生阻塞, 队列不满时, 阻塞结束; 队列为空时出队列, 产生阻塞, 队列不为空时, 阻塞结束.
线程安全的阻塞队列的最终实现:
class MyBlockingQueue{
//数组实现循环队列
static int[] array = new int[100];
//队首
private int head;
//队尾
private int tail;
//队列长度
private int size;
//入队列
synchronized public void put(int value) throws InterruptedException {
if(size == array.length){
//如果队列满时入队列, 使用wait()产生阻塞
this.wait();
}
array[tail] = value;
++size;
if(++tail >= array.length){
tail = 0;
}
//这里元素入队列成功, 唤醒take()中的等待
this.notify();
}
//出队列
synchronized public Integer take() throws InterruptedException{
if(size == 0){
//如果队列为空时出队列, 使用wait()产生阻塞
this.wait();
}
int ret = array[head];
++head;
if(head >= array.length){
head = 0;
}
--size;
//这里元素出队列成功, 唤醒put()中的等待
this.notify();
return ret;
}
}
public class Demo {
static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) {
//创建生产者
Thread producer = new Thread(() ->{
//随机生成入队元素
Random random = new Random();
int num = 0;
//生产者中每次入队一个元素
while (true){
num = random.nextInt(101);
try {
queue.put(num);
System.out.println("生产者: 生产了"+num);
//当生产者生产速度低时, 消费者的消费速度也会降低
//Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//消费者
Thread consumer = new Thread(()->{
while (true){
try {
int num = queue.take();
System.out.println("消费者: 消费了"+num);
//当生产速度远大于消费速度时, 如果造成阻塞队列满, 此时消费者每消费一次, 生产者才会生产
//Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
上述代码实现了一个最简单的生产者 - 消费者模型, 元素入队列一次表示一次生产, 元素出队列一次表示一次消费; 生产者和消费者中的Thread.sleep(1000)分别代表了生产速度慢和消费速度慢的情况, 当生产速度较慢时, 消费速度会随之降低; 当生产速度过快时, 一旦阻塞队列满了, 生产者就不会继续生产, 直到阻塞队列不为空.