目录
在我们上图的代码当中,如果把while改成if,那么就会存在一个问题:wait()被唤醒之后,此时的if条件,一定不成立了吗?
一、阻塞队列的特点
阻塞队列,也是一种先进先出的数据结构。和普通的队列(Queue)对比,它新增了两个功能:
①如果队列为空,执行出队列,按照Queue的规则,会抛出异常。但是在阻塞队列当中,会发生阻塞。直到有其他线程执行入队列的操作。
②如果队列满了,再执行入队列,那么执行入队列的线程会发生阻塞等待。直到有其他线程执行出队列的操作
二、生产者,消费者
在并发编程当中,生产者就是生产数据的线程,消费者就是消费数据的线程。
存在问题:
如果生产数据的线程处理速度比较快,而消费数据的线程处理速度比较慢,那么生产者将会等待消费者消费完成之后,才能继续生产数据。
反之,如果生产数据的线程生产比较慢,消费数据的线程消费数据比较快,那么消费数据的线程将需要等待生产数据的线程生产完毕之后,才可以继续消费。
从以上的场景可以看出生产者和消费者之间的耦合度还是比较高的,因此,就引入了阻塞队列,来降低生产者和消费者之间的耦合度。
解决问题——引入生产者消费者模式
生产者和消费者之间,是通过一个容器来解决生产者和消费者之间的强耦合问题。
由上图可以参考得出:生产者仅仅负责生产数据,消费者仅仅负责获取/消费数据,生产者和消费者之间并没有直接通信,而是通过一个阻塞队列之间通信。
生产者仅仅负责把数据生产完成之后放入到阻塞队列当中,消费者仅仅负责把数据取出来,然后供自己使用。
也就是说,生产者把数据生产出来之后,直接把数据放到阻塞队列当中,无需等待消费者获取。
消费者需要获取数据的时候,直接从阻塞队列当中获取,无需等待生产者生产数据,这就是生产者消费者模式
三、阻塞队列,Java实现
属性:
①此处考虑需要使用一个数组来实现。
②需要定义一个头指针,一个尾指针。
③size属性记录队列当中的实际存储个数。
方法:
①put方法:"生产者"线程专门调用的方法。
往阻塞队列当中存放元素。
当队列满的时候,如果仍然有线程调用put方法,那么会进入WAITING状态。
直到有"消费者"线程尝试从队列当中取出数据,然后把阻塞的"生产者“线程给唤醒。
②get方法:"消费者"线程专门调用的方法。
取出阻塞队列当中的元素。
当阻塞队列为空的时候,如果仍然有线程调用get方法,那么也会进入WAITING状态。
直到有“生产者”线程尝试往线程当中存放数据,存放之后唤醒“消费者”线程。
执行顺序分析:(图解)
在我们上图的代码当中,如果把while改成if,那么就会存在一个问题:wait()被唤醒之后,此时的if条件,一定不成立了吗?
如图 :
在我们的代码当中,这里其实确实是被唤醒之后,if语句一定不成立了。if和while都可以。
原因:在我们的代码设计当中,put()和get()方法都是同步方法,也就是:
当队列为满的时候,如果"生产者"想继续往队列当中存放元素,会因为条件size==elem.length满足而进入到if语句以内,调用wait()方法进入阻塞等待的状态。
当被唤醒的时候,一定是要等“消费者”取出元素之后,由“消费者”来唤醒正在wait()的线程
当队列为空的时候,同理。
那么,可以理解为:在上面的代码(put方法)当中,"生产者"在被唤醒之前,已经由"消费者"取出了元素,破环了size==elem.length这个条件了,因此不需要再使用while循环的语句重复判断一次了。
但是,看一下wait()方法的Object类的注释:
因此,在使用形如:
synchronized(obj){
if(条件)
obj.wait()
}
}
这样的语句时候,应当改成:
synchronized(obj){
while(条件)
obj.wait()
}
}
确保wait()被唤醒之前,已经满足了条件。
四、JUC当中的阻塞队列
常见的阻塞队列有:
LinkedBlockingQueue,底层是一个链表实现的。
ArrayBlockingQueue,底层是一个数组实现的。
PriorityBlockingQueue,底层是一个优先级队列(堆)实现的。要求存放的元素不可以是空的,一定是可以比较的。
代码实现(链表的阻塞队列)
观察运行结果:
可以看到,线程一直没有结束,一直阻塞。