一.BlockingQueue:
BlockingQueue是java.util.concurrent包下的一个队列类,从1.5开始,用于解决高并发环境中的队列应用,主要是生产者消费者问题
支持两个附加操作的Queue,这两个操作是获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。
也就是:
阻塞添加:当队列满了之后又有元素想要添加进来,添加元素的线程会被阻塞,直到队列非满之后,唤醒添加线程,元素被添加进来
阻塞删除:当队列空了之后又有线程想删除元素,该线程会被阻塞,直到队列非空之后,唤醒删除元素的线程,元素被删除。
来看看BlockingQueue接口里的方法:
boolean add(E e);
/*将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用
的空间,则抛出 IllegalStateException。*/
boolean offer(E e);
/**
将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的
空间,则返回 false。
**/
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
/** 将指定元素插入此队列中,在到达指定的等待时间前等待可用的空间(如果有必要)。
* Retrieves and removes the head of this queue, waiting if necessary
* until an element becomes available.
*
* @return the head of this queue
* @throws InterruptedException if interrupted while waiting
*/
E take() throws InterruptedException;
/**获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
*/
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
/**获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
*/
int remainingCapacity();
/**
返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的附加元素数量;如果没有内部
限制,则返回 Integer.MAX_VALUE。
**/
boolean remove(Object o);
/**
从此队列中移除指定元素的单个实例(如果存在)。
**/
public boolean contains(Object o);
/**
如果此队列包含指定元素,则返回 true。
*/
int drainTo(Collection<? super E> c);
/**
移除此队列中所有可用的元素,并将它们添加到给定 collection 中。
*/
int drainTo(Collection<? super E> c, int maxElements);
/**
最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。
**/
这里我们把上述操作进行分类
插入方法:
add(E e) : 添加成功返回true,失败抛IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则返回 false。
put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞
删除方法:
remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若队列为空,则返回 null
take():获取并移除此队列头元素,若没有元素则一直阻塞。
检查方法
element() :获取但不移除此队列的头元素,没有元素则抛异常
peek() :获取但不移除此队列的头;若队列为空,则返回 null。
BlockingQueue 的实现类:
ArrayBlockingQueue
DelayQueue
LinkedBlockingDeque
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
我们先用ArrayBlockingQueue实现一个生产者消费者的模型:
package com;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockQueue {
public static void main(String[] args) {
ArrayBlockingQueue<Colo> queue = new ArrayBlockingQueue<Colo>(5);
for (int i = 0; i < 5; i++) {
new Thread(new Producer(queue)).start();
}
for (int i = 0; i < 5; i++) {
new Thread(new Consumer(queue)).start();
}
}
}
class Colo {
}
class Consumer implements Runnable {
private ArrayBlockingQueue<Colo> queue;
public Consumer(ArrayBlockingQueue<Colo> queue) {
this.queue = queue;
}
public void consume() {
try {
System.out.println(Thread.currentThread().getName() + "消费了" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
// TimeUnit.SECONDS.sleep(1000);
while (true) {
Thread.sleep(1000);
consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Producer implements Runnable {
private final ArrayBlockingQueue<Colo> queue;
Producer(ArrayBlockingQueue<Colo> queue) {
this.queue = queue;
}
public void consume() {
Colo c = new Colo();
try {
queue.put(c);
System.out.println(Thread.currentThread().getName() + "生产了" + c);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000);
;
consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
Thread-2生产了com.Colo@b88b7fb
Thread-4生产了com.Colo@5585ed8f
Thread-7消费了com.Colo@5585ed8f
Thread-6消费了com.Colo@b88b7fb
Thread-9消费了com.Colo@6ea3659
Thread-5消费了com.Colo@5da9d59
Thread-3生产了com.Colo@6ea3659
Thread-0生产了com.Colo@531e98a5
Thread-8消费了com.Colo@531e98a5
Thread-1生产了com.Colo@5da9d59
Thread-5消费了com.Colo@b1d7392
Thread-2生产了com.Colo@54f58fa
Thread-9消费了com.Colo@5721161e
Thread-8消费了com.Colo@5b42194f
**/
接下来探究ArrayBlockingQueue的实现原理:
- 一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。 这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试
put
成满的队列的元件将导致在操作阻挡; 尝试take
从空队列的元件将类似地阻塞。
此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true
的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。
该类及其迭代器实现了Collection和Iterator接口的所有可选方法。
这个类是Java Collections Framework的成员 --------------------------------From Jdk 1.8 api
他的属性有:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 存储数据的数组 */
final Object[] items;
/**获取数据的索引,主要用于take,poll,peek,remove方法 */
int takeIndex;
/**添加数据的索引,主要用于 put, offer, or add 方法*/
int putIndex;
/** 队列元素的个数 */
int count;
/** 控制并非访问的锁 */
final ReentrantLock lock;
/**notEmpty条件对象,用于通知take方法队列已有元素,可执行获取操作 */
private final Condition notEmpty;
/**notFull条件对象,用于通知put方法队列未满,可执行添加操作 */
private final Condition notFull;
/**
迭代器
*/
transient Itrs itrs = null;
}
ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞,这里先看看其内部成员变量
他的主要方法:
boolean add(E e)
在插入此队列的尾部,如果有可能立即这样做不超过该队列的容量,返回指定的元素 true成功时与抛出 IllegalStateException如果此队列已满。
boolean offer(E e)
如果可以在不超过队列容量的情况下立即将其指定的元素插入该队列的尾部,则在成功时 false如果该队列已满,则返回 true 。
boolean offer(E e, long timeout, TimeUnit unit)
在该队列的尾部插入指定的元素,等待指定的等待时间,以使空间在队列已满时变为可用。
E peek()
检索但不删除此队列的头,如果此队列为空,则返回 null 。
E poll()
检索并删除此队列的头,如果此队列为空,则返回 null 。
E poll(long timeout, TimeUnit unit)
检索并删除此队列的头,等待指定的等待时间(如有必要)使元素变为可用。
void put(E e)
在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用。
E take()
检索并删除此队列的头,如有必要,等待元素可用。
E put(E e)
在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用。
那么他是如何通过ReentrantLock 和 两个Condition来实现阻塞的呢
先自己用condition来写一个生产者与消费者的模型:
package com.company.bank;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Condition2 {
private static final Lock lock = new ReentrantLock();
private static final Condition un_empty = lock.newCondition();
private static final Condition un_full = lock.newCondition();
static private Queue<Integer> queue = new LinkedList<Integer>();
public static void main(String[] args) throws InterruptedException {
Consumer consumer = new Consumer();
Produce produce = new Produce();
consumer.start();
produce.start();
Thread.sleep(30000);
consumer.interrupt();
produce.interrupt();
}
static class Consumer extends Thread {
@Override
public void run() {
super.run();
while(true){
consume();
}
}
private void consume() {
lock.lock();
try {
while (queue.size()==0) {
System.out.println("队列空,阻塞消费线程");
un_empty.await();
}
queue.poll();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "取走一个元素" + "还剩" + queue.size());
System.out.println("唤醒生产线程");
un_full.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class Produce extends Thread {
@Override
public void run() {
super.run();
while(true) {
produce();
}
}
private void produce() {
lock.lock();
try {
while (queue.size() == 10) {
System.out.println("队列已满,生产者被阻塞,等待空间");
un_full.await();
}
queue.offer(1);
Thread.sleep(1000);
System.out.println("向队列取中插入一个元素,队列剩余空间:" + (10 - queue.size()));
un_empty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
之前拿ArrayBlockingQueue api 实现的生产者消费者是利用了他的两个阻塞方法 take 和 put 实现的,对比自己手写的condition 实现的生产者消费者模型,其实他们底层是一模一样的,知道怎么写基础版的就能知道ArrayBlockingQueue底层原理
首先来看看put()的源码:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
这里一旦队列满了就用notfull将线程挂起,下面出现了一个enqueue的私有方法:
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();
}
注意这里就出现了notEmpty.signal()这里基本和手写的实现逻辑一模一样了。
private void produce() {
lock.lock();
try {
while (queue.size() == 10) {
System.out.println("队列已满,生产者被阻塞,等待空间");
un_full.await();
}
queue.offer(1);
Thread.sleep(1000);
System.out.println("向队列取中插入一个元素,队列剩余空间:" + (10 - queue.size()));
un_empty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
那么再看看take的实现源码:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//队列一旦满就需要不空,此时被挂起,需要消费者的消费然后用notempty.signal唤醒生产者线程
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)
itrs.elementDequeued();
notFull.signal();
return x;
}