阻塞队列
最近在研究《Java并发编程实战》,里面有专门介绍阻塞队列BlockingQueue
阻塞队列提供了可阻塞的put和take方法:
- 如果队列已经满了,put方法将阻塞直到有空间可用
- 如果队列为空,那么take方法将会阻塞直到有元素可用
看一个例子
BlockingQueue是一个接口,先看一下ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。ArrayBlockingQueue使用时必须指定大小
先设置队列长度2,通过构造方法指定
put元素
put方法用于向队列尾部添加元素,队列大小为2,存储3个元素,会造成put第3个元素时线程阻塞
@Test
public void testPut() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
blockingQueue.put("张三");
blockingQueue.put("李四");
blockingQueue.put("张三");
System.out.println("添加了3个人");
}
输出结果:
后面的"添加了3个人"一直不能输出,因为blockingqueue在put第2个元素"李四"之后就被阻塞了,后面的语句都不能执行
take元素
take方法用于从队尾获取元素,队列大小为2,存储了2个元素,会造成take第3个元素时线程阻塞
@Test
public void testTake() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
blockingQueue.put("张三");
blockingQueue.put("李四");
for (int i = 0; i < 3; i++) {
System.out.println(blockingQueue.take());
}
System.out.println("添加了3个人");
}
多种存取方法比较
在前面,演示了put/take这种阻塞方法的使用,但是看下API,里面有好几组存取方法
添加元素
- put:如果队列已经满了,put方法将阻塞直到有空间可用
- add:如果队列已经满了,则返回异常
- offer:如果元素添加成功,则返回true,否则返回false
put上面已经演示了,就不再赘述。
接着看下add:add方法在添加元素的时候,若超出了度列的长度会直接抛出异常:
@Test
public void testAdd() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
blockingQueue.add("张三");
blockingQueue.add("李四");
blockingQueue.add("张三");
System.out.println("添加了3个人");
}
输出结果如下,说明这个方法是非阻塞的,直接返回Queue full:队列已满
再看下offer:offer方法在添加元素时,如果发现队列已满无法添加的话,会直接返回false
@Test
public void testOffer() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
boolean of1 = blockingQueue.offer("张三");
boolean of2 = blockingQueue.offer("李四");
boolean of3 = blockingQueue.offer("张三");
System.out.println("添加了3个人");
System.out.println("元素是否获取成功:" + of1);
System.out.println("元素是否获取成功:" + of2);
System.out.println("元素是否获取成功:" + of3);
}
输出:说明添加前2个元素时成功,返回true;到第3个元素满了,则添加失败,返回false
获取元素
- take:如果队列为空,那么take方法将会阻塞直到有元素可用
- poll:取走BlockingQueue里排在首位的对象,取不到时返回null
take上面已经演示了,就不再赘述。
接着看下poll:先在队列放入2个元素,再从里面取出3个元素,
@Test
public void testPoll() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
blockingQueue.put("张三");
blockingQueue.put("李四");
for (int i = 0; i < 3; i++) {
System.out.println(blockingQueue.poll());
}
System.out.println("添加了3个人");
}
输出:第2个元素没有,则返回null;这个方法还支持超时设置,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
生产者-消费者
阻塞队列支持生产者-消费者这种设计模式。而BlockingQueue简化了生产者-消费者的实现过程。
先定义一个生产者线程ProducerDemo1,实现Runnable接口,通过BlockingQueue来存放生产的"商品"
public class ProducerDemo1 implements Runnable {
private BlockingQueue<String> blockingQueue;
public ProducerDemo1(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000);
blockingQueue.put("商品");
System.out.println(Thread.currentThread().getName() + "生产商品");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
现定义一个消费者线程ConsumerDemo1,实现Runnable接口,通过BlockingQueue消费"商品"
public class ConsumerDemo1 implements Runnable {
private BlockingQueue<String> blockingQueue;
public ConsumerDemo1(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(1000);
blockingQueue.take();
System.out.println(Thread.currentThread().getName() + "消费商品");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
开始测试一下,生成2个线程来分别生产,消费商品
public class ProducerAndConsumerDemo1 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
new Thread(new ProducerDemo1(blockingQueue)).start();
new Thread(new ConsumerDemo1(blockingQueue)).start();
}
}
输出:生产者,消费者交替输出(因为为了方便观察,队列只能存放一个元素)
线程安全问题?
通过生产者,消费者例子,不知道大家会不会有一个疑问,如果是多个线程同时生产,消费,会不会有并发问题呢?
答案是不会,看下put方法的源码,可以发现利用ReentrantLock加锁了,所以不会出现同步问题。take方法类似
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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();
}
}