1.阻塞队列
2.生产者消费者模型
3.标准库阻塞队列
4.自定义阻塞队列的实现
5.阻塞队列实现生产者消费者模型
1.阻塞队列是什么东西?
阻塞就是要等待,
1)当你饿了,下一步是吃饭,但是,你妈妈没做饭,所以你就阻塞等待,等到你妈妈做好饭,你再开始吃饭
2)阻塞队列的底层是一个循环队列,但是要加上阻塞的等待的性质
你要往队列中放元素,如果此时队列中的元素满了,插入元素的这个动作就会停下来等,而不是结束,等到这个满的队列中有元素出队列了,这时你上一步插入元素的动作就会接着往下执行~
反过来也一样,假如队列的元素空了,你准备从这个空队列里取元素,但是没元素给你取你就要停下来等到这个队列被插入元素后,你取元素这个动作才会继续,整个取元素的过程下来不会结束而是在等待
这个就是阻塞队列的原理~
2.生产者消费者模型?
供不应求,供过于求~ ----》 这是生产和消费的关系~
加上阻塞队列的场景就比如包饺子:你爸爸负责擀饺子皮,你和你妈妈负责包饺子(爸爸是生产者,你跟妈妈是消费者),如果你爸爸的速度很快,你跟妈妈才包好10个饺子,爸爸就已经擀好了100张饺子皮,此时你爸爸可以停下来玩会手机休息~反过来,你跟你妈妈包饺子的速度大于爸爸擀饺子皮的速度的话,你跟你妈妈可以停下来等爸爸擀多些饺子皮,你们娘俩再开始包饺子
以上就是生产者消费者模型的简单白话
3.标准库里的阻塞队列?
标准库里面有自带的阻塞队列
在普通队列里,我们主要的操控队列的方法有1.出队列 2.入队列 3.获取队首元素
但是阻塞队列中只有前两个方法,没有获取队首元素的方法
public class MyBlockingQueue2 {
public static void main(String[] args) throws InterruptedException {
//标准库阻塞队列
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue(100);//参数代表队列长度
//入队列
//blockingQueue.offer(1)//没有阻塞功能
blockingQueue.put(1);
blockingQueue.put(2);
blockingQueue.put(3);
//出队列
int n1 = blockingQueue.take();
System.out.println(n1);
int n2 = blockingQueue.take();
System.out.println(n2);
int n3 = blockingQueue.take();
System.out.println(n3);
}
}
4.自定义阻塞队列
实现一个阻塞队列:
1.实现一个普通循环队列
2.解决线程安全问题
3.实现队列的阻塞功能
4.队列细节问题的解决
//阻塞队列的模拟实现
//1.先写一个普通的循环队列
//2.解决线程安全问题
//3.阻塞实现-》1)队列空,出队列阻塞 2)队列满,插入阻塞 -》队列不会出现又满又空的情况
//小问题:wait可能会被提前唤醒,或者线程之间出现锁竞争导致线程不安全 -》把判断if改成while
class MyBlockingQueue{
public int[] items = new int[100];
public volatile int head = 0;
public volatile int tail = 0;
public volatile int size = 0;
//入队列
public void put(int val) throws InterruptedException {
synchronized (this){
//先判满
while (size >= items.length){
//此时队列为满,不能再插入元素,要阻塞等待
this.wait();
}
items[tail] = val;
tail++;
//入满了
if(tail >= items.length){
tail = 0;
}
size++;
this.notify();//这里是唤醒队列为空时的wait,到这里队列已经有元素了
}
}
//出队列
public Integer take() throws InterruptedException {//把返回值改成Integer可以返回null
synchronized (this){
//判空
while (size == 0){//用while防止wait被提前唤醒,wait会被interrupt唤醒
//此时队列为空,要阻塞等待,等待到队列加入一个元素
this.wait();
}
int ret = items[head];
head++;
if(head >= items.length){
head = 0;
}
size--;
this.notify();//这里唤醒队列满时的wait
return ret;
}
}
}
1.循环队列---》计数器或者浪费一个空间来实现
2.线程安全---》用synchronized给put和take加锁
3.阻塞实现---》用wait和notify来实现阻塞
4.细节问题----》wait可以被interrupt唤醒/如果在队列为空的情况下,线程1要take,线程2put,线程3take,并且线程1在wait状态,那么当线程2调用notify后,线程1会和线程3产生锁竞争,如果线程3获取锁,并take走了线程2插入的元素,那么线程1会报错
所以把判满和判空的条件改成了while,是为了避免以上情况,如果wait被提前唤醒,那么在判断一遍,不满足条件就继续wait;如果出现了锁竞争,也可以继续wait,避免出现错误
5.生产者消费者模型代码举例
public static void main(String[] args) {
//生产者消费者模型
MyBlockingQueue myBlockingQueue = new MyBlockingQueue();
Thread producer = new Thread(() -> {
int n = 1;
while (true){
try {
myBlockingQueue.put(n);
System.out.println("生产元素" + n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread customer = new Thread(() -> {
while (true){
try {
int n =myBlockingQueue.take();
System.out.println("消费元素" + n);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
producer.start();
customer.start();
}