本文标题大纲:
前言
在前面的系列文章中,我们介绍了一下 Java 中多线程的一些主要的知识点和多线程并发程序的设计和处理思想。包括线程的介绍、生命周期、线程的运行控制。之后介绍了如何确保 Java 多线程并发程序的正确性,即通过锁(ReentrantLock
、synchronized
)的思想来实现多线程执行顺序的控制等。如果你对这些还不熟悉,建议看一下前面的文章。接下来我们来看一下 Java 多线程中另一个重要的知识:线程池,在此之前,我们需要了解一下 Java 中的阻塞队列:
阻塞队列
何为阻塞队列呢?首先,这个名字中有一个 队列
,证明应该是一个提供了队列相关功能(存取物品)的东西,那么何为 阻塞
呢?这个词我们在前面的文章中已经见过多次了,明显是针对线程而言的。那么我们大概可以猜出这个阻塞队列大概是干什么的了:可以使得线程陷入阻塞状态的储存队列。
这么说其实意思是到了,但是具体在哪里会用到这个阻塞队列呢?不知道你还记不记得《操作系统》课程中讲过一个非常经典的问题:生产者和消费者问题。我们来一起看一下:
假设现在我们有一个固定容量的产品队列,里面放的是生产者产生的产品。
我们将生产者看做是一个线程,这个线程专门向这个产品队列中提供已经成熟的产品;
我们将消费者也看作是一个线程,这个线程专门从这个产品队列中取出生产者线程提供的产品。两者的操作过程可以用一张图来表示:
这个其实就是我们说的生产者、消费者模型,但是有一些问题需要解决:
1、产品队列的容量是有限的,也就是说产品队列中只能储存有限个产品,那么当生产者线程把产品送入产品队列时,应该检测产品队列是否已经饱和,如果饱和,那么证明生产者线程此时应该被阻塞(即不能继续输入产品到产品队列中),等到消费者线程从产品队列中取出产品之后,生产者线程才应该被唤醒,继续输送产品。
2、同样的问题也会在消费者线程中发生:当产品队列中的产品数目为 0 时,即产品队列中没有产品了,那么此时消费者线程不能取产品,也应该被阻塞,即每次消费者取产品的时候应该检测队列是否为空,如果为空的话,消费者线程被阻塞,直到生产者向产品队列中输送产品之后,消费者线程才应该被唤醒,继续从产品队列中取出产品。
3、最后,为了保证产品队列中数据的正确性,在生产者线程和消费者线程在进入产品队列输送 / 取出产品之前,线程应该获取产品队列中的锁资源,没有获取产品队列的锁资源的线程不能进入产品队列中执行操作,即同一个时刻生产者线程和消费者线程不能同时进入产品队列中执行操作。
自定义阻塞队列
我们可以把能够解决上面提出的 3 个问题的队列称之为阻塞队列,我们可以利用前面的知识去构造一个简单的自定义阻塞队列来完成这个功能了:
/**
* 自定义阻塞队列实现生产者、消费者模式:
*/
public static class CustomBlockingQueueTest {
// 自定义的阻塞队列
public static class CustomBlockingQueue<T> {
private Object[] elements = null;
// 当前元素个数
private int elementsCount = 0;
// 阻塞队列的锁资源
private ReentrantLock lock = new ReentrantLock();
// 队列已满的线程阻塞控制器
Condition fullLock = null;
// 队列为空的线程阻塞控制
Condition emptyLock = null;
public CustomBlockingQueue(int elementsCount) {
elements = new Object[elementsCount];
fullLock = lock.newCondition();
emptyLock = lock.newCondition();
}
/**
* 向队列末尾插入新元素的方法,如果队列元素已满,那么阻塞当前线程
*/
public void put(T ele) throws InterruptedException {
if (ele == null) {
throw new IllegalArgumentException("插入元素不能为空!");
} else {
// 构造同步块
lock.lock();
try {
// 如果当前队列已满,那么阻塞当前生产者线程
while (elementsCount == elements.length) {
System.out.println("队列已满,元素插入失败,线程被阻塞!");
fullLock.await();
}
// 将元素插入队列末尾
elements[elementsCount++] = ele;
// 队列中已经有元素,唤醒所有阻塞的消费者线程
emptyLock.signalAll();
System.out.println("插入元素,当前队列元素数量:" + elementsCount);
} finally {