博文部分参考:【Java 编程思想 第四版】+ 电子书总结
阻塞队列
1. 什么是阻塞队列?
-
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
-
阻塞队列提供了四种处理方法:
1.抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
2.返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
3.一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
4.超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
2. Java里的阻塞队列
自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,主要有以下几个:
- 1、ArrayBlockingQueue: 基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
- 2、LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
- 3、PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
- 4、DelayQueue: 基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
3.栗子一: 下面使用ArrayBlockQueue来实现的生产者消/费者模式
- 我们先来大概了解下什么是生产者消费者模型:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞
public class BlockQueue {
public static void main(String[] args)
{
BigPlate plate = new BigPlate();
for(int i = 0; i < 10; i++) // 先启动10个放鸡蛋线程
{
new Thread(new BigPlate.AddThread(plate)).start();
}
for(int i = 0; i < 10; i++) // 再启动10个取鸡蛋线程
{
new Thread(new BigPlate.GetThread(plate)).start();
}
}
}
/** 定义一个盘子类,可以放鸡蛋和取鸡蛋 */
class BigPlate
{
/** 装鸡蛋的盘子,大小为5 */
private BlockingQueue<Object> eggs = new ArrayBlockingQueue<>(5);
/** 放鸡蛋 */
public void putEgg(Object egg)
{
try
{
eggs.put(egg);// 向盘子末尾放一个鸡蛋,如果盘子满了,当前线程阻塞
} catch (InterruptedException e)
{
e.printStackTrace();
}
// 下面输出有时不准确,因为与put操作不是一个原子操作
System.out.println("放入鸡蛋");
}
/** 取鸡蛋 */
public Object getEgg()
{
Object egg = null;
try
{
egg = eggs.take();// 从盘子开始取一个鸡蛋,如果盘子空了,当前线程阻塞
} catch (InterruptedException e)
{
e.printStackTrace();
}
// 下面输出有时不准确,因为与take操作不是一个原子操作
System.out.println("拿到鸡蛋");
return egg;
}
/** 放鸡蛋线程 */
static class AddThread extends Thread
{
private BigPlate plate;
private Object egg = new Object();
public AddThread(BigPlate plate)
{
this.plate = plate;
}
public void run()
{
plate.putEgg(egg);
}
}
/** 取鸡蛋线程 */
static class GetThread extends Thread
{
private BigPlate plate;
public GetThread(BigPlate plate)
{
this.plate = plate;
}
public void run()
{
plate.getEgg();
}
}
栗子二: 要求不用阻塞队列,使用 LinkedList结合多线程知识实现生产者消/费者模式
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
class BlockingQueue<E>{
private final LinkedList<E> queue = new LinkedList<>();
private static int max;//表示堵塞队列存储的最大值
private static final int DEFAULT_MAX_VALUE = 10;
public BlockingQueue(){
this(DEFAULT_MAX_VALUE);
}
public BlockingQueue(int max) {
this.max = max;
}
//生产数据
public void put(E value){
synchronized (queue){
if (queue.size()>=max){
System.out.println(Thread.currentThread().getName()+":is full");
try {
//没有位置,当前生产数据就会等待
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"添加"+value);
queue.addLast(value);
queue.notifyAll();
}
}
//消费数据
public E take(){
synchronized (queue){
//判断是否有可消费的数据
if (queue.isEmpty()){
System.out.println(Thread.currentThread().getName()+":isEmpty");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
E result = queue.removeFirst();
System.out.println("删除"+result);
queue.notifyAll();
return result;
}
}
}
public class TestDemo13 {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new BlockingQueue<>();
new Thread("product"){
@Override
public void run() {
while (true){
queue.put((int)(Math.random()*1000));
}
}
}.start();
new Thread("Consumer"){
@Override
public void run() {
while (true){
queue.take();
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}