本文已参与「新人创作礼」活动,一起开启掘金创作之路。
书接上文
阻塞队列
先进先出
阻塞队列同样也是一个符合先进先出规则的队列
相比于普通的队列,阻塞队列多出了几个功能
1.线程安全
2.阻塞效果
1)如果队列为空,想要提取元素,那么就会阻塞,阻塞到有元素为止
2)如果队列为满,想要插入元素,那么就会阻塞,阻塞到不满为止
这个也就引出生产者消费者模型
生产者消费者模型
这个就好像是去买橘子,如果没有橘子,那你只能等到橘子有的时候才能买,
如果橘子滞销了很久,那工厂就不生产橘子了,直到橘子被卖出去了才再生产
那阻塞队列可以做到些什么呢?
这个可以用来削峰填谷。当访问量过高的时候不会崩溃。
java使用阻塞队列
让我们先来用一用阻塞队列
public class Demo19 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("hello"); //这边的报错是因为阻塞引起的,因为阻塞之后就要唤醒,唤醒呢又有可能打断,所以要报错
String s = queue.take(); //这个是拿元素
}
}
java实现
让我们来实现一下阻塞队列
class MyBlockingQueue{
private int[] value = new int[100];
//保存元素个数
private int size = 0;
//保存元素头
private int head = 0;
//保存元素尾
private int tail = 0;
private Object object = new Object();
public void put(int v){
synchronized(object){
if(size == value.length){
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return;
}
value[tail] = v;
tail++;
size++;
if(tail >= value.length){
tail = 0;
}
object.notify();
}
}
public Integer take(){
synchronized (object){
if(size == 0){
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}
int ret = value[head];
head++;
size--;
if(head >= value.length){
head = 0;
}
object.notify();
return ret;
}
}
}
从上面的代码可以看出几个重要的地方。
第一:上锁的位置,我们可以看到不管是take还是put我们都用到了value数组,而且没有哪个地方是没有用到的,所以我们直接全部锁上,大家可以选择这边的,还可以选择其他的方式,就比如直接在方法上面加锁。
第二:notify和wait的配合,我们发现,东西没有的时候,和东西满的时候我们都有wait让它们等待对方,当对方执行一个操作之后就会notify一下。这样wait的就可以被唤醒继续操作,这样的阻塞队列就完成了。
那么生产者消费者模型就可以按照上面写出来
private static MyBlockingQueue queue = new MyBlockingQueue();
首先我们先创建一个队列,让生产者和消费者在上面进行操作。之后我们创建生产者和消费者
生产者
Thread t1 = new Thread(()->{
int num = 0;
while(true){
System.out.println("生产了: " + num);
queue.put(num);
num++;
}
});
t1.start();
消费者
t1.start();
Thread t2 = new Thread(()->{
while(true){
System.out.println("消费了: " + queue.take());
}
});
t2.start();
然后就能让他们进行活动了,我们可以看见,生产者生产了一个,消费者就会消费一个,有时候,生产者会连着生产,消费者也会连着消费。这个也是之前提到的抢占式执行