目录
栈与队列
栈:先进后出,后进先出
队列:先进先出
什么是阻塞队列
在多线程领域:所谓阻塞,就是在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤醒
线程1往阻塞队列里添加元素,线程2从阻塞队列里移除元素
以蛋糕店为例,假设去买蛋糕的时候,如果柜子里没有蛋糕,则购买蛋糕这项工作将被阻塞,如果柜子里的蛋糕都满了,则生产蛋糕这项工作将被阻塞
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来
*为什么要用?
如果不用的话,我们需要自己去控制这些细节,尤其是还要兼顾效率和线程安全,这会为程序带来不小的复杂度
*为什么需要使用BlockingQueue
使用BlockingQueue,我们不需要关心什么时候阻塞线程,什么时候唤醒线程,因为BlockingQueue都包办了。
架构梳理
*种类分析
BlockingQueue核心方法
示例
public class BlockingQueueDemo {
static BlockingQueue<String> b = new ArrayBlockingQueue<>(3);
/**
* 抛出异常
* 当阻塞队列满时,再往队列里add插入元素会抛 IllegalStateException:Queue full
* 当阻塞队列空时,再往队列里remove移除元素会抛 NoSuchElementException
*/
@Test
public void testException() {
for (int i = 0; i < 3; i++) {
b.add("a");
}
for (int i = 0; i < 3; i++) {
b.remove();
}
System.out.println(b);
}
/**
* 特殊值
* 插入方法,成功ture失败false
* 移除方法,成功返回出队列的元素,队列里没有就返回null
*/
@Test
public void testSpecial() {
for (int i = 0; i < 3; i++) {
System.out.println(b.offer("a"));
}
for (int i = 0; i < 3; i++) {
System.out.println(b.poll());
}
}
/**
* 一直阻塞
* 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产者线程直到put数据or响应中断退出
* 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用
*/
@Test
public void tesBlock() throws InterruptedException {
for (int i = 0; i < 3; i++) {
b.put("a");
}
for (int i = 0; i < 3; i++) {
b.take();
}
}
/**
* 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过时间后生产者线程会退出
* 等待一段时间,过了时间之后,执行移除,返回结果与poll()一样
* @throws InterruptedException
*/
@Test
public void testOfferTime() throws InterruptedException {
for (int i = 0; i < 3; i++) {
System.out.println(b.offer("3"));
}
//System.out.println(b.offer("3", 3L,TimeUnit.SECONDS));
for (int i = 0; i < 3; i++) {
System.out.println(b.poll());
}
System.out.println(b.poll(3l, TimeUnit.SECONDS));
System.out.println(b);
}
}
SynchronousQueue
- 与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue
- 每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然.
示例
public static void main(String[] args) {
BlockingQueue<String> sq = new SynchronousQueue<>();
// 队列中有元素的时候,无法进行存储,必须移除之后,才能继续存储
// 这种不存储元素的效果就是SynchronousQueue
new Thread(() -> {
try {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + "\tput第" + i + "次");
sq.put(String.valueOf(i));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 1; i <= 3; i++) {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "\t" + sq.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
*你都在什么地方使用阻塞队列?
-
线程池
-
消息中间件
-
生产者消费者模式
传统版:JUC线程间的通信
阻塞队列版:import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 一个综合了volatile/CAS/AtomicInteger/BlockingQueue/线程交互/综合引用的一个小demo * * 根据一个FLAG来对线程进行控制 * true,生产一个消费一个,使用阻塞队列就可以实现,不需要手动控制wait/notify,总共来5轮,之后设置为false * false,同时关闭生产者消费者,生产者停止生产,消费者给定一个时间,超过两秒钟没有在生产者中获取到新的东西,消费者关闭 */ public class ProdConsumer_BlockQueueDemo { public static void main(String[] args) throws Exception { MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10)); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t生产线程启动"); try { myResource.myProd(); } catch (Exception e) { e.printStackTrace(); } }, "Prod").start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t消费线程启动\n"); try { myResource.myConsumer(); System.out.println(); System.out.println(); } catch (Exception e) { e.printStackTrace(); } }, "Consumer").start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("\n5秒钟时间到,大老板叫停了main线程"); myResource.stop(); } } class MyResource { private volatile boolean FLAG = true;//默认开启,进行生产+消费,为了保证线程之间的可见性,必须使用volatile private AtomicInteger atomicInteger = new AtomicInteger();//i++ BlockingQueue<String> blockingQueue = null;//传抽象接口,可以使其得到最大程度的复用 public MyResource(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; } /** * 生产 * * @throws Exception */ public void myProd() throws Exception { String data = null; boolean retVal; while (FLAG) { //原子版i++ data = atomicInteger.incrementAndGet() + ""; retVal = blockingQueue.offer(data, 2L, TimeUnit.SECONDS); if (retVal) { 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() + "\tFLAG=false,大老板叫停了,生产动作结束"); } /** * 消费 * * @throws Exception */ public void myConsumer() throws Exception { String result = null; while (FLAG) { result = blockingQueue.poll(2l, TimeUnit.SECONDS); //如果取不到值了,消费者退出 if (null == result || result.equalsIgnoreCase("")) { FLAG = false; System.out.println(Thread.currentThread().getName() + "\t超过2秒钟没有取到蛋糕,消费者退出"); return; } System.out.println(Thread.currentThread().getName() + "\t移除队列" + result + "成功"); } } public void stop() throws Exception { this.FLAG = false; } }