目录
前言
前面的文章中,提到了队列,如自己实现一个队列和Java中提供了队列的实现LinkedList,而本文(分为上篇和下篇)中主要介绍的也是队列,但它是一种特殊的队列-----阻塞队列
引出阻塞队列
在使用我们自己实现的队列或者Java中提供好的LinkedList时,可能会出现一些问题,如在多线程环境下可能会出现数据不一致问题等,再或者想实现当队列空时取出元素不立即返回或抛异常,而是等待执行时间或一直等到队列有元素时取出,阻塞队列就能解决这些问题
普通队列的线程安全问题
测试案例
java
package algorithm.queue;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.stream.IntStream;
public class JavaQueueTest {
public static void main(String[] args) throws InterruptedException {
// 队列:多个线程共享
Queue<Integer> queue = new LinkedList<>();
// 用于控制所有线程执行完
CountDownLatch countDownLatch = new CountDownLatch(10);
// 这里有10个线程,每个线程分别往队列中添加100个元素
IntStream.range(0, 10).forEach(no -> new Thread(() -> {
for (int i = 0; i < 100; i++) {
queue.offer(i);
}
System.out.println(Thread.currentThread().getName() + "-执行结束...");
countDownLatch.countDown();
}, "线程" + no).start());
countDownLatch.await();
// 正常来讲,这里队列的大小应该是10*100=1000
// 但实际结果却是小于1000,且每次运行的结果不一样
// 存在线程安全问题
System.out.println(queue.size());
}
}
运行结果
第一次运行,结果如下

第二次运行,结果如下

第三次运行,结果如下

队列空时执行取出元素
java
package algorithm.queue;
import java.util.LinkedList;
import java.util.Queue;
public class JavaQueueTest {
public static void main(String[] args) {
// Java中提供的队列
Queue<Object> queue = new LinkedList<>();
// 此时队列为空
System.out.println(queue.isEmpty());
// 执行取出元素
Object ele = queue.poll();
// 立即返回null
System.out.println(ele);
}
}
实现阻塞队列
定义接口
老样子,第一步还是先把接口定义出来,后续有多种实现方式都可以对该接口进行实现,可以提高其可扩展性
java
package algorithm.queue;
public interface BlockingQueue {
/**
* 添加元素,若队列已满,则等待其他元素被移除
*/
void put(Object e) throws InterruptedException;
/**
* 移除元素,若队列已空,则等待其他元素被添加到队列中
*/
Object take() throws InterruptedException;
/**
* 元素个数
*/
int size();
}
用单锁实现阻塞队列
java
package algorithm.queue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 基于链表并使用一把锁的方式来实现阻塞队列
*/
public class OneLockListBlockingQueue implements BlockingQueue {
// 虚拟头节点和虚拟尾节点
private Node dummyHead;
private Node dummyTail;
// 元素个数
private final AtomicInteger size = new AtomicInteger(0);
// 默认最大容量
private static final int MAX_CAPACITY = Integer.MAX_VALUE - 8;
private final int maxCapacity;
// 定义一把锁
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
// 未指定队列最大容量,使用默认值
public OneLockListBlockingQueue() {
maxCapacity = MAX_CAPACITY;
initHeadAndTail();
}
// 初始化虚拟头尾节点的关系
private void initHeadAndTail() {
dummyHead = new Node(null);
dummyTail = new Node(null);
dummyHead.next = dummyTail;
dummyTail.prev = dummyHead;
}
// 指定了队列的最大容量
public OneLockListBlockingQueue(int maxCapacity) {
this.maxCapacity = maxCapacity;
initHeadAndTail();
}
@Override
public void put(Object e) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
try {
lock.lock();
while (size.get() == maxCapacity) {
// 阻塞等待
condition.await();
}
// 从尾部添加节点元素
Node newNode = new Node(e);
Node prev = dummyTail.prev;
dummyTail.prev = newNode;
newNode.prev = prev;
newNode.next = dummyTail;
prev.next = newNode;
// 元素个数加一
size.getAndIncrement();
// 唤醒其他等待线程
condition.signalAll();
} finally {
lock.unlock();
}
}
@Override
public Object take() throws InterruptedException {
try {
lock.lock();
while (size.get() == 0) {
// 阻塞等待
condition.await();
}
// 从头部删除元素
Node removeNode = dummyHead.next;
Node next = dummyHead.next.next;
dummyHead.next = next;
next.prev = dummyHead;
removeNode.prev = null;
removeNode.next = null;
// 元素个数减一
size.getAndDecrement();
// 唤醒其他等待线程
condition.signalAll();
return removeNode.val;
} finally {
lock.unlock();
}
}
@Override
public int size() {
return size.get();
}
// 定义数据节点,这里使用双向链表
private static class Node {
Object val;
Node prev;
Node next;
public Node(Object val) {
this.val = val;
}
public Node(Object val, Node prev, Node next) {
this.val = val;
this.prev = prev;
this.next = next;
}
}
}
用上述相同的测试案例来测试自实现的阻塞队列的线程安全问题
java
package algorithm.queue;
import java.util.concurrent.CountDownLatch;
import java.util.stream.IntStream;
/**
* 测试自实现的单锁实现的阻塞队列
*/
public class OneLockListBlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
// 阻塞队列:多个线程共享
BlockingQueue queue = new OneLockListBlockingQueue();
// 用于控制所有线程执行完
CountDownLatch countDownLatch = new CountDownLatch(10);
// 这里有10个线程,每个线程分别往队列中添加100个元素
IntStream.range(0, 10).forEach(no -> new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
queue.put(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "-执行结束...");
countDownLatch.countDown();
}, "线程" + no).start());
countDownLatch.await();
// 无论执行多少次,结果都是1000
System.out.println(queue.size());
}
}

生产者-消费者模型的体现
可以使用自定义的阻塞队列实现生产一个消费一个的效果
测试代码
java
package algorithm.queue;
public class ProducerConsumerTest {
public static void main(String[] args) {
// 阻塞队列
BlockingQueue queue = new OneLockListBlockingQueue(1);
// 生产者
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
// 生产者生产速度:100ms一次
Thread.sleep(100);
queue.put(i);
System.out.println(Thread.currentThread().getName() + "-" + i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "producer").start();
// 消费者
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
// 消费者消费速度:500ms一次
Thread.sleep(500);
Object val = queue.take();
System.out.println(Thread.currentThread().getName() + "-" + val);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "consumer").start();
}
}
测试结果
