阻塞队列简介及常见场景

本文介绍了Java中的阻塞队列BlockingQueue,它是一种线程安全的队列,提供了阻塞的插入和移除方法。 BlockingQueue在生产者/消费者模式中有广泛应用,常见的阻塞队列实现包括ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue和SynchronousQueue。ArrayBlockingQueue基于数组,适合生产者和消费者速度匹配的场景;LinkedBlockingQueue默认无界,适合动态增长的需求;PriorityBlockingQueue支持优先级排序;DelayQueue支持延迟获取元素;SynchronousQueue则是一个不存储元素的阻塞队列,适合传递性场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BlockingQueue简介

BlockingQueue 继承了 Queue 接口,是队列的一种。Queue 和 BlockingQueue 都是在 Java 5
中加入的。阻塞队列(BlockingQueue)是一个在队列基础上又支持了两个附加操作的队列,两个附加操作(实现阻塞功能):
        支持阻塞的插入方法put: 队列满时,队列会阻塞插入元素的线程,直到队列不满。
        支持阻塞的移除方法take: 队列空时,获取元素的线程会等待队列变为非空

BlockingQueue常用方法示例


当队列满了无法添加元素,或者是队列空了无法移除元素时:
1. 抛出异常:add、remove、element
2. 返回结果但不抛出异常:offer、poll、peek
3. 阻塞:put、take

BlockingQueue应用场景


BlockingQueue 是线程安全的,我们在很多场景下都可以利用线程安全的队列来优雅地解决
我们业务自身的线程安全问题。

比如说,使用生产者/消费者模式的时候,我们生产者只需要往队列里添加元素,而消费者只需要从队列里取出它们就可以了。常见于类似于MQ之类的消息组件中

因为阻塞队列是线程安全的,所以生产者和消费者都可以是多线程的,不会发生线程安全问
题。生产者/消费者直接使用线程安全的队列就可以,而不需要自己去考虑更多的线程安全问题。
这也就意味着,考虑锁等线程安全问题的重任从“你”转移到了“队列”上,降低了我们开发的
难度和工作量

常见阻塞队列


BlockingQueue 接口的实现类都被放在了 juc 包中,它们的区别主要体现在存储结构上或对元素
操作上的不同,但是对于take与put操作的原理,却是类似的。

  • ArrayBlockingQueue                基于数组结构实现的一个有界阻塞队列
  • LinkedBlockingQueue              基于链表结构实现的一个无界阻塞队列,也可指定容量
  • PriorityBlockingQueue             支持按优先级排序的无界阻塞队列(初始有界,可不断扩容)
  • DelayQueue                             基于PriorityBlockingQueue实现的无界阻塞队列
  • SynchronousQueue                 不存储元素的阻塞队列
  • LinkedTransferQueue               基于链表结构实现的一个无界阻塞队列
  • LinkedBlockingDeque               基于链表结构实现的一个双端阻塞队列

ArrayBlockingQueue


ArrayBlockingQueue是最典型的有界阻塞队列,其内部是用数组存储元素的,初始化时需要
指定容量大小,利用 ReentrantLock 实现线程安全。
在生产者-消费者模型中使用时,如果生产速度和消费速度基本匹配的情况下,使用
ArrayBlockingQueue是个不错选择;当如果生产速度远远大于消费速度,则会导致队列填满,大
量生产线程被阻塞。
使用独占锁ReentrantLock实现线程安全,入队和出队操作使用同一个锁对象,也就是只能有
一个线程可以进行入队或者出队操作;这也就意味着生产者和消费者无法并行操作,在高并发场
景下会成为性能瓶颈。

LinkedBlockingQueue

LinkedBlockingQueue是一个基于链表实现的阻塞队列,默认情况下,该阻塞队列的大小为
Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列
代表它几乎没有界限,队列可以随着元素的添加而动态增长,但是如果没有剩余内存,则队列
将抛出OOM错误。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的
时候建议手动传一个队列的大小。
LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。
LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都
有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。

SynchronousQueue


SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作
put必须等待消费者的移除操作take。

应用场景

SynchronousQueue非常适合传递性场景做交换工作,生产者的线程和消费者的线程同步传
递某些信息、事件或者任务。
SynchronousQueue的一个使用场景是在线程池里。如果我们不确定来自生产者请求数量,
但是这些请求需要很快的处理掉,那么配合SynchronousQueue为每个生产者请求分配一个消费
线程是处理效率最高的办法。Executors.newCachedThreadPool()就使用了
SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则
会重复使用,线程空闲了60秒后会被回收。

PriorityBlockingQueue


PriorityBlockingQueue是一个无界的基于数组的优先级阻塞队列,数组的默认长度是
11
,虽然指定了数组的长度,但是可以无限的扩充,直到资源消耗尽为止,每次出队都返
回优先级别最高的或者最低的元素。默认情况下元素采用自然顺序升序排序,当然我们也可以
通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能
保证同优先级元素的顺序。
优先级队列PriorityQueue: 队列中每个元素都有一个优先级,出队的时候,优先级最高的
先出。

demo样例

package com.test.jucdemo.blokingqueue;

import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.PriorityBlockingQueue;

/**
 * @author 
 */
public class PriorityBlockingQueueDemo {

    public static void main(String[] args) throws InterruptedException {
        //创建优先级阻塞队列  Comparator为null,自然排序
        PriorityBlockingQueue<Integer> queue=new PriorityBlockingQueue<Integer>(5);
//         自定义Comparator
//        PriorityBlockingQueue queue=new PriorityBlockingQueue<Integer>(
//                5, new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return o2-o1;
//            }
//        });


        Random random = new Random();
        System.out.println("put:");
        for (int i = 0; i < 5; i++) {
            int j = random.nextInt(100);
            System.out.print(j+"  ");
            queue.put(j);
        }

        System.out.println("\ntake:");
        for (int i = 0; i < 5; i++) {
            System.out.print(queue.take()+"  ");
        }


    }
}

应用场景


电商抢购活动,会员级别高的用户优先抢购到商品
银行办理业务,vip客户插队

DelayQueue


DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储
元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元
素,只有在延迟期满时才能从队列中提取元素。延迟队列的特点是:不是先进先出,而是会按照
延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。
它是无界队列,放入的元素必须实现 Delayed 接口,而 Delayed 接口又继承了
Comparable 接口,所以自然就拥有了比较和排序的能力,代码如下:

使用场景

商城订单超时关闭、异步短信通知功能、关闭空链接、缓存失效、任务超时处理。

package com.test.jucdemo.blokingqueue.delayqueue;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 模拟订单支付,创建订单
 */
public class OrderPay {

    static String[] str = new String[]{ "成功", "支付中", "订单初始化" };

    public static String getTime(long time) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date(time);
        String currentTime = formatter.format(date);
        return currentTime;
    }

    public static void main(String[] args) throws InterruptedException {
        OrderOverTimeClose.getInstance().init();

        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    // 创建初始订单
                    long createTime = System.currentTimeMillis();
                    String currentTime = getTime(createTime);
                    //设置订单过期时间
                    String overTime = getTime(createTime + 10000);// 10s后超时
                    String orderNo = String.valueOf(new Random().nextLong());
                    OrderInfo order = new OrderInfo();
                    order.setOrderNo(orderNo);
                    order.setExpTime(overTime);
                    int random_index = (int) (Math.random() * str.length);
                    //设置订单状态
                    order.setStatus(str[random_index]);
                    order.setCreateTime(currentTime);
                    //插入订单到延时队列中  注意并发场景下使用单例模式,避免重复创建对象;
                    OrderOverTimeClose.getInstance().orderPutQueue(order, currentTime, overTime);
                }
            };
            service.execute(run);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.test.jucdemo.blokingqueue.delayqueue;

import java.util.Objects;
import java.util.concurrent.DelayQueue;

/**
 * 使用延时队列DelayQueue实现订单超时关闭
 * 后台守护线程不断的执行检测工作
 *
 */

public class OrderOverTimeClose {

    private volatile static OrderOverTimeClose oderOverTimeClose = null;

    private OrderOverTimeClose() {

    }

    /**
     * 单例模式,双检查锁模式,在并发环境下对象只被初始化一次
     */
    public static OrderOverTimeClose getInstance() {
        if (oderOverTimeClose == null) {
            synchronized (OrderOverTimeClose.class) {
                if (oderOverTimeClose == null) {
                    oderOverTimeClose = new OrderOverTimeClose();
                }
            }
        }
        return oderOverTimeClose;
    }

    /**
     * 守护线程
     */
    private Thread mainThread;

    /**
     * 启动方法
     */
    public void init() {
        mainThread = new Thread(() -> execute());
        mainThread.setDaemon(true);
        mainThread.setName("守护线程-->");
        mainThread.start();
    }

    /**
     * 创建空延时队列
     */
    private DelayQueue<OrderInfo> queue = new DelayQueue<OrderInfo>();

    /**
     * 读取延时队列,关闭超时订单
     */
    private void execute() {
        while (true) {
            try {
                if (queue.size() > 0) {
                    //从队列里获取超时的订单
                    OrderInfo orderInfo = queue.take();
                    // 检查订单状态,是否已经成功,成功则将订单从队列中删除。
                    if (Objects.equals(orderInfo.getStatus(), "成功")) {
                        //TODO 支付成功的订单处理逻辑
                        System.out.println("线程:" + Thread.currentThread().getName() + ",订单号:"
                                + orderInfo.getOrderNo() + ",订单状态:"
                                + orderInfo.getStatus() + ",订单创建时间:"
                                + orderInfo.getCreateTime()
                                + ",订单超时时间:" + orderInfo.getExpTime() + ",当前时间:"
                                + OrderPay.getTime(System.currentTimeMillis()));
                        Thread.sleep(2000);
                    } else {
                        // TODO 超时未支付订单处理逻辑
                        System.out.println("线程:" + Thread.currentThread().getName() + ",订单号:"
                                + orderInfo.getOrderNo() + ",变更订单状态为:超时关闭"
                                + ",订单创建时间:"
                                + orderInfo.getCreateTime()
                                + ",订单超时时间:" + orderInfo.getExpTime() + ",当前时间:"
                                + OrderPay.getTime(System.currentTimeMillis()));
                        Thread.sleep(2000);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 插入订单到延时队列中
     */
    public void orderPutQueue(OrderInfo orderInfo, String createTime,
                              String overTime) {
        System.out.println("订单号:" + orderInfo.getOrderNo()
                +",订单状态:"+ orderInfo.getStatus()
                +",订单创建时间:"+ createTime
                + ",订单过期时间:" + overTime);
        queue.add(orderInfo);
    }

}
package com.test.jucdemo.blokingqueue.delayqueue;

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;


/**
 * 订单对象
 */
public class OrderInfo implements Delayed,Serializable {

    private static final long serialVersionUID = 1L;
    private String orderNo;// 订单号
    private String status;// 订单状态
    private String expTime;// 订单过期时间
    private String createTime;//订单创建时间

    /**
     * 用于延时队列内部比较排序:当前订单的过期时间 与 队列中对象的过期时间 比较
     */
    @Override
    public int compareTo(Delayed o) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long nowThreadtime = 0;
        long queueThreadtime = 0;
        try {
            nowThreadtime = formatter.parse(this.expTime).getTime();
            queueThreadtime = formatter.parse(((OrderInfo) o).expTime).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return Long.valueOf(nowThreadtime).compareTo(Long.valueOf(queueThreadtime));
    }

    /**
     * 时间单位:秒
     * 延迟关闭时间 = 过期时间 - 当前时间
     */
    @Override
    public long getDelay(TimeUnit unit) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long time = 0;
        try {
            time = formatter.parse(this.expTime).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return time - System.currentTimeMillis();
    }

    public String getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }

    public String getExpTime() {
        return expTime;
    }

    public void setExpTime(String overTime) {
        this.expTime = overTime;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值