深入理解阻塞队列
前言
建议先看一下这篇分享,深入理解Condition
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。
- 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入的元素,直到队列不满
- 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者从队列里取元素的线程。阻塞队列就是生产者用来存放元素,消费者用来获取元素的容器
例子
举一个多生产者,多消费者的例子,队列的大小为3,即队列大小为3时,生产者就不再生产
public class Producer implements Runnable {
private ArrayList list;
private int capacity;
public Producer(ArrayList queue,int capacity) {
this.list = queue;
this.capacity = capacity;
}
@Override
public void run() {
synchronized (list) {
while (true) {
while (list.size() == capacity) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object object = list.add(new Object());
System.out.println(Thread.currentThread().getName() + " 生产");
list.notifyAll();
}
}
}
}
注意消费者和生产者都是用的notifyAll()[通知所有阻塞的线程]方法,而不是notify()[通知一个阻塞的线程]方法,因为有可能出现“生产者”唤醒“生产者”,消费者“唤醒”消费者的情况,因此有可能造成死锁,这里以1个消费者,3个生产者为例说一下,消费者1获得锁还没产品呢,阻塞,接着生产者1获得锁生产完了,然后生产者2获得锁后生产完了,再然后生产者3获得锁生产完了,最后生产者1获得锁了,然后阻塞了,现在好了生产者和消费者都阻塞了,造成了死锁。notifyAll()则不会造成死锁,接着上面的步骤,生产者3生产完了释放锁后,会通知所有阻塞的线程,因此消费者1肯定有机会拿到锁来进行消费
public class Consumer implements Runnable {
private List list;
public Consumer(List queue) {
this.list = queue;
}
@Override
public void run() {
synchronized (list) {
while (true) {
while (list.size() == 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object object = list.remove(0);
System.out.println(Thread.currentThread().getName() + " 消费");
list.notifyAll();
}
}
}
}
public class Test {
public static void main(String[] args) {
ArrayList list = new ArrayList(3);
for (int i = 0; i < 3; i++) {
new Thread(new Producer(list, 3), "生产者" + i).start();
new Thread(new Consumer(list), "消费者" + i).start();
}
}
}
一部分结果
生产者0 生产
生产者0 生产
生产者0 生产
消费者1 消费
消费者1 消费
消费者1 消费
生产者1 生产
把这个实例用阻塞队列来改写,先自己写一个阻塞队列,实现BlockingQueue接口,这里只展示了一部分重写的方法
public class MyBlockingQueue<E> implements BlockingQueue<E> {
private int capacity;
private List<E> list;
public MyBlockingQueue(int capacity) {
this.capacity = capacity;
this.list = new ArrayList(capacity);
}
@Override
public void put(E e) throws InterruptedException {
synchronized (this) {
if (list.size() == capacity) {
this.wait();
}
list.add(e);
this.notifyAll();
}
}
@Override
public E take() throws InterruptedException {
synchronized (this) {
while (list.size() == 0) {
this.wait();
}
E value = list.remove(0);
this.notifyAll();
return value;
}
}
}
public class Producer implements Runnable {
private BlockingQueue queue;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
queue.put(new Object());
System.out.println(Thread.currentThread().getName() + " 生产");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable {
private BlockingQueue queue;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Object object = queue.take();
System.out.println(Thread.currentThread().getName() + " 消费");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
BlockingQueue queue = new MyBlockingQueue(3);
for (int i = 0; i < 3; i++) {
new Thread(new Producer(queue), "生产者" + i).start();
new Thread(new Consumer(queue), "消费者" + i).start();
}
}
}
这里将BlockingQueue的实现改为ArrayBlockingQueue,程序运行结果一样,和我们之前的例子比较,BlockingQueue其实就是不用我们自己写阻塞和唤醒的部分,直接看一下ArrayBlockingQueue的源码,其实和我自己实现的差不多,只不过并发这部分源码用的是ReentLock,而我用的是synchronized
源码
基于jdk1.8.0_20
先看属性
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** The queued items */
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
}
构造函数
// 设置同步队列的大小和锁是否公平
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
put方法
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 响应中断的方式上锁
lock.lockInterruptibly();
try {
while (count == items.length)
// 将当前线程加入notFull这个等待队列
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
// 循环数组实现
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
take方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
// 将当前线程加入notEmpty这个等待队列
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
// 放到数组的最后一个了,下一次从头开始放
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
//更新iterators,不再分析
itrs.elementDequeued();
notFull.signal();
return x;
}
其他方法就不再分析,基本上就是上锁,然后进行相应的操作
最后说一下LZ的理解,个人感觉用ArrayBlockingQueue实现生产者和消费者,比我上面用synchronized的方式应该快很多,毕竟ArrayBlockingQueue只会是生成者通知消费者,或者消费者通知生产者,而synchronized不是,会造成很多不必要的锁竞争,当然并没有实验,有时间可以试试
参考书籍
《Java并发编程的艺术》
文章转载自公众号:Java识堂