java-03 数据结构-队列-LinkedBlockingQueue队列详解及应用

  LinkedBlockingQueue是Java中的一个线程安全的队列实现,它实现了BlockingQueue接口。它是基于链表结构实现的,可以存储任意类型的元素。LinkedBlockingQueue的特点是插入和移除元素是具有固定顺序的,即先进先出(FIFO)顺序。同时,LinkedBlockingQueue支持可选的容量限制,可以在初始化时指定最大容量,如果队列满了,插入操作将会被阻塞,直到队列中出现空位。同样,如果队列为空,移除操作也会被阻塞,直到队列中有元素可供移除。
  除了普通的队列操作,LinkedBlockingQueue还提供了一些其他方法,比如获取队列中的元素数量、判断队列是否为空、清空队列等。由于LinkedBlockingQueue是线程安全的,所以它可以被多个线程同时使用而不需要额外的同步措施。因此,它常被用于多线程环境下的生产者-消费者模式中。

如果你觉得我分享的内容或者我的努力对你有帮助,或者你只是想表达对我的支持和鼓励,请考虑给我点赞、评论、收藏。您的鼓励是我前进的动力,让我感到非常感激。

1 常用方法

add(E e): 向队列尾部添加一个元素,如果队列已满,则抛出IllegalStateException异常。

offer(E e): 向队列尾部添加一个元素,如果队列已满,则返回false。

put(E e): 向队列尾部添加一个元素,如果队列已满,则线程被阻塞直到队列有空间可用。

poll(): 移除并返回队列头部的元素,如果队列为空,则返回null。

take(): 移除并返回队列头部的元素,如果队列为空,则线程被阻塞直到有元素可用。

size(): 返回队列中元素的个数。

remove(Object o): 从队列中移除指定的元素,如果元素不存在,则返回false。

contains(Object o): 判断队列中是否包含指定的元素。

clear(): 清空队列中的所有元素。

peek(): 返回队列头部的元素,但不移除。

2 底层实现

  它基于链表实现,可以在队列的两端进行插入和删除操作。LinkedBlockingQueue 可以限制队列的容量,当队列已满时,插入操作会被阻塞,直到队列有空闲空间可用;当队列为空时,删除操作也会被阻塞,直到有元素可供删除。

LinkedBlockingQueue 的主要实现原理如下:

  • 内部使用了一个双向链表来存储元素,链表的头节点和尾节点分别表示队列的头和尾。
  • 使用两个锁来控制并发访问,一个是putLock,一个是takeLock。putLock 控制插入操作的并发访问,takeLock 控制删除操作的并发访问。
  • 当队列为空时,删除操作会被阻塞,直到有元素可供删除。此时会获取 takeLock 锁,并调用 takeLock 的条件变量 notEmpty.await() 来进行阻塞等待,直到队列不为空。
  • 当队列已满时,插入操作会被阻塞,直到队列有空闲空间可用。此时会获取 putLock 锁,并调用 putLock 的条件变量 notFull.await() 来进行阻塞等待,直到队列有空闲空间可用。
  • 当插入操作或删除操作完成后,会分别调用 notFull.signal() 和 notEmpty.signal() 来唤醒等待中的线程。

3 示例

    public static void main(String[] args) throws InterruptedException {
        // 创建一个容量为3的LinkedBlockingQueue
        LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(3);

        // 向队列中添加元素
        queue.add(1);
        queue.put(2);
        queue.offer(3);
        System.out.println("队列中的元素:" + queue);

        // 获取队列头部的元素
        int head = queue.peek();
        System.out.println("队列头部的元素:" + head);

        // 移除队列头部的元素
        int removed = queue.poll();
        System.out.println("移除的元素:" + removed);
        System.out.println("队列中的元素:" + queue);
    }

4 总结

  LinkedBlockingQueue 是 Java.util.concurrent 包下的一个类,它是一个线程安全的、基于链表的、可选有界的队列。它实现了 BlockingQueue 接口,可以在多线程环境下安全地使用。

特点:

  • 链表实现:LinkedBlockingQueue 是基于链表的数据结构实现的队列,它可以高效地支持插入和删除操作。
  • 线程安全:LinkedBlockingQueue 内部使用了锁来保证多线程环境下的线程安全。
  • 可选有界:LinkedBlockingQueue 可以选择是否有界,即队列的容量是否有限。如果容量有限,当队列满时,插入操作会被阻塞,直到队列不满;当队列为空时,移出操作会被阻塞,直到队列不空。如果容量无限,则插入和移出操作不会被阻塞。
  • 公平性:LinkedBlockingQueue 可以选择是否公平,即在多个等待线程时,遵循先进先出的原则进行阻塞和唤醒。如果选择公平性,线程会按照先来后到的顺序被唤醒,只有等待最久的线程才能获取锁。

应用场景:

  • 生产者-消费者模型:LinkedBlockingQueue 可以作为线程之间安全传递数据的缓冲区,生产者线程往队列中插入数据,消费者线程从队列中移出数据,利用队列的阻塞特性可以实现线程间的同步。
  • 线程池的任务队列:LinkedBlockingQueue 可以作为线程池的任务队列,任务被提交到队列中,由线程池的工作线程从队列中取出任务并执行。
  • 数据流水线处理模式:LinkedBlockingQueue 可以作为数据流水线中不同阶段之间的缓冲区。
  • 其他需要线程安全的队列操作的场景。

5 实践应用

5.1 业务介绍

批量创建(导入)回传任务包:
  任务信息入库。界面上触发单个或者批量回传操作

执行回传任务:
  1. 批量或者单个执行回传任务时,会将此任务添加到队列中,排队执行
  2. 进行相关业务操作,数据准备,文件准备
  3. 调用下游接口,将任务执行信息传递给下游,执行回传操作,平台记录回传任务跟踪记录,随后定时任务跟踪回传进度

删除任务包:
  可以多选任务包进行删除, 后台过滤出能够被删除的任务包进行删除,不能够被删除的任务包向前台返回【后台对任务包里的任务进行判断,如果任务包里的任务存在执行回传的任务,则不能被删除】

取消回传任务:
如果此任务在队列中,并且状态未待执行,才会被取消

5.2 实现方案【使用LinkedBlockingQueue实现生产-消费模型】

controller层【有返回值】 - 生产者

  1. 获取可执行的任务列表
  2. 判断队列容量【1000】,如果小于本批添加的任务量,接口返回添加任务失败。
  3. 遍历任务列表,逐个进行判断,将符合要求的任务添加到内存【状态为1】及任务列表。

service层【执行队列里的任务】 - 消费者

  1. 服务启动后,开始异步监听队列,【使用@PostConstruct,及其方法内容executorService.execute(() -> {while (true) {}},起到监听作用】
  2. 逐个处理队列里的任务【阻塞获取任务】
  3. 查看任务状态是否被取消,被取消将不继续后续流程
  4. 开始执行回传任务
  5. 任务信息入库,同时内存中移除此任务

5.3 示例代码

  1. 初始化缓存【存放任务状态】和消息队列
/**
 * 存放在排队或者正在执行的Map
 */
public static final Map<String, TaskPackDefiniteBean> TASK_STATUS_MAP = new ConcurrentHashMap<>();
/**
 * 数据回传队列
 */
public static final LinkedBlockingQueue<TaskPackDefiniteBean> BACK_HAUL_QUEUE = new LinkedBlockingQueue<>(10000);
  1. 消费者监听队列
/**
 * 初始化回传任务队列
 */
@PostConstruct
@Override
public void initBackHaulTask() {
    executorService.execute(() -> {
        while (true) {
            // 1 获取队列中的任务
            Optional<TaskPackDefiniteBean> taskOptional = getTask();
            if (taskOptional.isPresent()) {
                TaskPackDefiniteBean taskPackDefinite = taskOptional.get();
                String taskId = taskOptional.get().getTaskId();
                LOGGER.info("execute the back haul task,taskId:{} . Start", taskId);
                // 2 判断任任务是否被取消
                if (TASK_STATUS_MAP.containsKey(taskId)) {
                    executeBackHaulTask(taskPackDefinite, taskId);
                } else {
                    LOGGER.warn("The task has been canceled,taskId:{}", taskId);
                }
            }
        }
    });
}

private void executeBackHaulTask(TaskPackDefiniteBean taskPackDefinite, String taskId) {
    // 3 开始执行回传任务
    int taskStatus = TASK_STATUS_SUCCESS;
    String resultDesc = SUCCESS_FLAG;
    try {
        executeBackHaulTask(taskPackDefinite);
        LOGGER.info("execute the back haul task,taskId:{}. Success", taskId);
    } catch (CommonServiceException exp) {
        taskStatus = TASK_STATUS_FAILURE;
        resultDesc = STATUS_CODE_AND_ERROR_MSG_RELATION.get(exp.getErrorCode());
        ExceptionUtils.printExceptionInfo(exp);
        LOGGER.error("execute the back haul task,taskId:{}. Failure", taskId);
    }
    // 4 任务信息入库,同时内存中移除此任务
    updateTask(taskPackDefinite, taskStatus, resultDesc);
    TASK_STATUS_MAP.remove(taskId);
    LOGGER.info("execute the back haul task,taskId:{}. Finish", taskId);
}

private Optional<TaskPackDefiniteBean> getTask() {
    try {
        return Optional.of(BACK_HAUL_QUEUE.take());
    } catch (InterruptedException exp) {
        LOGGER.error("Failed to obtain the task from the queue");
        ExceptionUtils.printExceptionInfo(exp);
    }
    return Optional.empty();
}
  1. 生产者产生消息
	// 4.2 存放在内存和队列中
    String taskId = taskPackDefinite.getTaskId();
    BackHaulServiceImpl.TASK_STATUS_MAP.put(taskId, taskPackDefinite);
    BackHaulServiceImpl.BACK_HAUL_QUEUE.put(taskPackDefinite);

在这里插入图片描述生产者有多个。消费者只有一个

5.4 扩展思考

  1. LinkedBlockingQueue队列只能在单服务内使用,不能夸服务
  2. 多生产者,单消费者 用 LinkedBlockingqueue。如果需要性能更好,多消费的情况。请参考ConcurrentLinkedQueue队列详解
  3. 在处理任务时,并发度为1。这个可以参考算法执行服务任务并发度设计。方法主体设计成异步,取队列里的任务时,每次都判断并发度。
/**
 * 初始化回传任务队列
 */
@PostConstruct
@Override
public void initBackHaulTask() {
    executorService.execute(() -> {
        while (true) {
            // 首先获取正在运行的任务数
            int runningNum = 2;
            // 只有当前处理的任务数小于当节点并发度时,才会获取新的消息
            if (runningNum < SCAN_RUNNING_NUM) {
                // 1 获取队列中的任务
                Optional<TaskPackDefiniteBean> taskOptional = getTask();
                if (taskOptional.isPresent()) {
                    TaskPackDefiniteBean taskPackDefinite = taskOptional.get();
                    String taskId = taskOptional.get().getTaskId();
                    LOGGER.info("execute the back haul task,taskId:{} . Start", taskId);
                    // 2 判断任任务是否被取消
                    if (TASK_STATUS_MAP.containsKey(taskId)) {
                        // TODO 修改成异步方法
                        executeBackHaulTask(taskPackDefinite, taskId);
                    } else {
                        LOGGER.warn("The task has been canceled,taskId:{}", taskId);
                    }
                }
            }
        }
    });
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值