目录
这篇文章只是主要给大家介绍下阻塞队列的代码实现,这里不阐述使用的好处,以及为啥要使用。
1.阻塞队列是什么?
阻塞队列是一种特殊的数据结构,遵守的依旧是我们在学习数据结构的时候普通的队列的原则——“先进先出”的原则。
阻塞队列是一种线程安全的数据结构,并且它具有以下特征:
1.当队列满的时候,继续入队就会产生阻塞,直到有其他的线程从队列中取走元素。
2.当队列空的时候,继续出队就会产生阻塞,直到有其他的线程在队列中插入元素。
阻塞队列在我们的生活中也是有应用场景的,就是“消费者生产者模型”,这是一种非常典型的开发模型。
2.阻塞队列的实现。
这里呢所讲的阻塞队列就是在普通的队列上面所进行的改动。大家在观看之前应该要对队列有一定的了解,这样才方便后续的查看代码。防止有同学对队列没有了解,这里呢我就先简单的说下。
下面我通过简单的例子让大家有更加深入地印象,在我们前几年的时候我们是全国各地都是爆发疫情的,这就导致了我们的出行是非常不方便的,而且我们后面每天还需要做核酸,这就给大家带来了些许不便,我们在做核酸的时候就是需要进行排队的,我们去的迟的话就需要在后面进行排队,直到前面的人走了我们就可以向前面进行移动。以我们为中心看的话,我们相对于后面的人看的话,我们是去的最早的,所以我们也是最早做的,这就是“先进先出”的特点。
2.1插入操作的实现。
普通的队列插入操作代码:
public void put(String elem) {
//队已满
if (array.length <= size){
System.out.println("队列已满,无法插入元素。");
return;
}
//队未满
array[tail] = elem;
tail++;
if(tail >= array.length){
tail = 0;
}
size++;
}
上述代码是我们学习队列的时候,所学到的代码逻辑。这个在单线程下是没有问题的,但是在多线程下就是存在 bug 的。
下面大家可以先想一下应该怎么修改,这里我就直接说了。
1.我们后面是要进行多个线程同时进行访问的,我们进行插入操作的时候是存在线程不安全的情况的,我们可以去看插入和删除的代码块的时候会发现两个的真实操作是互相影响的,所以我们为了避免在后面多线程下出现问题,我们就在插入操作的时候进行了线程等待,当数组中元素放满的时候就会进行访问插入的线程进行阻塞,使用的是wait()方法,那我们在哪里唤醒这个等待呢,就是在我们删除一个元素的时候 我们就继续可以进行插入操作,所以我们的唤醒操作就要放到删除操作完之后。
2.第二个就是将 if() 换成 while() 。因为我们在进行操作的时候,要是只是在多线程的情况下只判断一次满的情况,万一我们的情况出现了,等待唤醒之后,情况依然是这样,那么我们就没有进行改变,程序就错误的执行下去了,所以我们要将 if 换成 while 。
3.还有一个就是在判断满的时候,我们进行了防御性编程,防止后面我们万一出现不可发生的情况将程序搞崩溃。
多线程下的代码展示:
//添加元素
public void put(String elem) throws InterruptedException{
synchronized (lock){
//队已满
while (array.length <= size){
lock.wait();
}
//队未满
array[tail] = elem;
tail++;
if(tail >= array.length){
tail = 0;
}
size++;
lock.notify();
}
}
还有就是我们的阻塞队列就是元素满的时候禁止进行插入,没有元素的时候就会禁止出元素。我们将等待 wait() 添加到那个位置就满足了这个条件。
2.2删除操作的实现。
普通队列进行的删除操作:
//去除元素
public String take() {
//队已空
if (size == 0){
System.out.println("队列中没有元素无法进行去除操作。");
return null;
}
//队未空
String take = array[head];
head++;
if(head >= array.length){
head = 0;
}
size--;
return take;
}
上述代码需要在多线程条件下进行优化的情况是和在插入操作是相同的,就不过多进行解释了,大家可以参考插入的操作。
多线程下的代码展示:
//去除元素
public String take() throws InterruptedException {
synchronized (lock){
//队已空
while (size == 0){
lock.wait();
}
//队未空
String take = array[head];
head++;
if(head >= array.length){
head = 0;
}
size--;
lock.notify();
return take;
}
}
2.3其他变量代码。
代码展示:
private String[] array = null ;
private int head = 0;//标记队前
private int tail = 0;//标记队尾
private int size = 0;//用于判断是否队满的条件
//初始化
public MyQueue(int caption){
array = new String[caption];
}
//创建锁对象
private static final Object lock = new Object();
2.4测试类。
代码展示:
public static void main(String[] args) throws InterruptedException {
MyQueue queue = new MyQueue(2000);
//添加元素
Thread t = new Thread(()->{
int count = 0;
try{
while(true){
queue.put("" + count);
System.out.println("添加一个元素:" + count);
count++;
Thread.sleep(1000);
}
}catch(InterruptedException e){
e.printStackTrace();
}
});
//删除元素
Thread h = new Thread(()->{
try{
while(true){
String elem = queue.take();
System.out.println("删除一个元素:" + elem);
Thread.sleep(1000);
}
}catch(InterruptedException e){
e.printStackTrace();
}
});
t.start();
h.start();
t.join();
h.join();
}
上述代码是我们在main 方法里面进行执行的代码,我们创建了两个线程,一个线程去执行插入元素的操作;另外一个线程去执行删除元素的操作。
下面我们先去执行两个线程都进行睡眠操作的代码,结果如下:
我们观察上述的代码执行结果,我们可以看到我们是添加了一个元素之后,之后就去执行删除一个元素的操作。
那么我们将插入线程中的sleep 操作进行注释之后呢,我们再去观察结果:
我们观察上述的结果,发现了我们直接一口气添加了2000个元素,之后才进行的删除操作,那是因为我们刚开始的时候将数组的大小设置为2000个,所以他最大就添加2000 个元素;还有就是我们阻塞队列的性质导致的。
我们后面要是将两个的休眠时间进行调成,之后的打印结果也会发生变化。上述就是阻塞队列的所有操作了。