阻塞队列的实现(线程案例)
一.什么是阻塞队列?
1.如果对于一个满的队列,还要把元素入队列,此时这个队列就会阻塞等待,一直阻塞到这个队列不满为止,从而把这个元素入队列!
2.如果对于一个空的队列,还要从队列拿出元素,此时这个队列就会阻塞等待,一直阻塞到这个队列不空为止,从而把这个元素拿出队列!
二.阻塞队列有什么作用:
1.解耦合作用
什么是耦合度:
耦合度高:就是模块之间联系比较紧密,彼此之间互相影响
耦合度低:就是模块之间联系没有那么紧密,也就是模块之间影响小。
此时引入阻塞队列,就可以有效降低模块之间的耦合度,起到了解耦合的作用!!
看图👇
2.削峰填谷作用
三.代码的实现
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
queue.put("666");
String s=queue.take();
System.out.println(s);
s=queue.take();
System.out.println(s);
}
}
此时阻塞等待👇
四.自己模拟实现
//实现阻塞队列
class MyBlockingQueue{
//这个是模拟实现队列
private String[] elems = null;
private int head = 0;
private int tail = 0;
private int size = 0;
private Object locker = new Object();
//实现构造方法👇
public MyBlockingQueue(int capacity){
elems = new String[capacity];
}
//放进元素
public void put (String elem) throws InterruptedException {
synchronized (locker){
//超出队列长度!
//使用while循环,是防止线程唤醒后只判断一次,让被唤醒的线程再判断一次
while(size >= elems.length){
locker.wait();
}
//往这里加元素!!!
elems[tail] = elem;
tail++;
//这里面是循环队列,可使这块空间能够被循环利用!,所以需要tail = 0;
if(tail == elems.length){
tail = 0;
}
size++;
locker.notify();
}
}
//拿出元素
public String take() throws InterruptedException {
String elem = null;
//队列里面没有元素,阻塞等待
synchronized (locker){
while(size == 0){
locker.wait();
}
//拿出一个元素
elem = elems[head];
head++;
if(head == elems.length){
head = 0;
}
size--;
locker.notify();
}
return elem;
}
}
public class ThreadDemo10 {
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue(1000);
//创建两个线程实现生产者和消费者模型
Thread t1 =new Thread(()->{
int n = 1;
while(true){
try {
myBlockingQueue.put(n+"");
System.out.println("生产元素"+n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2 = new Thread(()->{
while(true){
try {
String n = myBlockingQueue.take();
System.out.println("消费元素"+n);
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//线程的启动!!
t1.start();
t2.start();
}
}
代码中的一些问题
是否能够把里面这个while改成if👇
if(size >= elems.length){
locker.wait();
}
答案是不能的,为什么呢
假设这个队列的容量是1,且此时容量已满。假设有3个线程t1,t2、t3.
t1和t2都调用put(),此时t1线程进入wait()状态,由于wait()状态,t1线程释放了锁。然后
t2也进入了锁里面,进入wait()状态,也释放了锁。
t3线程执行take(),从线程中拿出了一个元素,然后进行notify(),随机唤醒了其中一个线程,假设唤醒了t1线程,t1线程把一个元素放进了队列,此时队列满了,然后notify(),唤醒了t2
线程,由于if操作已经执行过了,所以它要把元素放进队列,由于队列容量为1,所以t2无法把元素放进队列中,此时代码便出现了问题。
由此可见,使用while()可以使线程唤醒之后,再判断一次条件,进而避免了线程安全的问题!
五.生产者-消费者模型
这就可以使用阻塞队列来完成
此时这个生产元素的速度要比消费元素速度要快,所以队列会先满了,然后阻塞等待,最后就是消费一个元素,生产一个元素,趋于平衡。
那么就会有人说,队列的容量不是1000,为什么会出现1001
原因就在于:线程的调度是不确定的。
当生产元素1000已经达到了队列的容量,这是毋庸置疑的。然后线程进行消费元素,然后notify(),唤醒了生产元素的线程,此时这个消费元素还没有打印,然后这个生产元素的线程打印了生产元素1001。