在juc包中,BlockingQueue很好的解决了多线程中数据高效传输的问题,通过实现BlockingQueue这些线程安全的队列类,给开发人员带来了很多便利。本文初步介绍下BlockingQueue堵塞队列的相关知识。
堵塞队列的好处
多线程领域:在某些情况下会挂起线程(即堵塞), 一旦条件满足,被挂起的线程又会被自动唤醒。 使用BlockingQueue(接口)好处是我们不需要关心什么时候需要堵塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。
常见队列
1.ArrayBlockingQueue:由数组结构组成的有界堵塞队列
2.LinKBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)堵塞队列
3.SynchronousQueue: 不存储元素的堵塞队列,也即单个元素的队列
我们在写个demo验证下SynchronousQueue队列:
SynchronousQueue没有容量,与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue.每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
public class TestBlockQueue {
public static void main(String[] args) {
testSynchronousQueue();
}
private static void testSynchronousQueue() {
SynchronousQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "---put1");
try {
blockingQueue.put("1");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---put2");
try {
blockingQueue.put("2");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---put3");
try {
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "---" + blockingQueue.take());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "---" + blockingQueue.take());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "---" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程2").start();
}
}
运行结果:
线程1---put1
线程2---1
线程1---put2
线程2---2
线程1---put3
线程2---3
当线程1往SynchronousQueue中put1时,需要等待sleep5秒,线程2从中取出后,才会进行下一个数值的put操作。
到底何为堵塞
试图从空的堵塞队列中获取元素的线程将会被堵塞,直到其他线程往空的队列中插入新的元素,同样试图往已满的堵塞队列中添加新元素的线程同样也会被堵塞,直到其他的线程从列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增。
BlockingQueue的核心方法
1.放入数据
(1)offer(e):表示如果可能的话,将e加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程);
(2)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
(3)put(e):把e加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
(4)add(e):把e加到BlockingQueue里,当堵塞队列满时,在往队列中add插入元素会抛异常IllegalStateException:Queue full;
2. 获取数据
(1)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
(2)poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则时间超时还没有数据可取,返回失败。
(3)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
生产者与消费者案例
在没有使用堵塞队列之前,我们在生产者消费者的案例中用到了synchronized或者Lock去实现,我们需要考虑什么时候让线程堵塞,什么时候唤醒线程;现在我们使用堵塞队列来实现,我们不需要关心什么堵塞线程,什么时候唤醒线程,BlockingQueue一手操办好了,我们直接使用就行。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ProdConsumerQueueDemo {
public static void main(String[] args) throws InterruptedException {
MyResource myResource = new MyResource(new LinkedBlockingQueue<String>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"启动成功");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
},"prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"启动成功");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
},"comsumer").start();
TimeUnit.SECONDS.sleep(5);
System.out.println("5s停止生产消费");
myResource.stop();
}
}
class MyResource {
private volatile boolean flag = true;
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data = null;
boolean result;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
result = this.blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (result) {
System.out.println(Thread.currentThread().getName() + "\t 插入数据" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入数据" + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t 停止生产");
}
public void myConsumer() throws Exception {
String data = null;
while (flag) {
data = this.blockingQueue.poll(2L, TimeUnit.SECONDS);
if(null == data || "".equals(data)){
System.out.println(Thread.currentThread().getName() + "\t 超过2s获取不到数据");
flag = false;
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费数据" + data + "成功");
}
}
public void stop() {
this.flag = false;
}
}
运行结果: