谈谈 ArrayBlockingQueue 的使用

本文详细介绍了BlockingQueue的概念、特点及其实现原理,包括其在Java并发编程中的应用,如ArrayBlockingQueue的使用,以及各种操作如add、offer、remove、poll等的特性与差异。

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

什么是 BlockingQueue?

简介

    BlockingQueue 是由数组支持的有界阻塞队列。此队列对元素FIFO(先进先出)进行排序。队列的开头是已在队列中最长时间的元素。队列的尾部是最短时间出现在队列中的元素。新元素插入到队列的尾部,并且队列检索操作在队列的开头获取元素

    此类支持可选的公平性策略,用于订购正在等待的生产者和使用者线程。默认情况下,不保证此排序。但是,将公平性设置为true构造的队列将按FIFO顺序授予线程访问权限。公平通常会降低吞吐量,但会减少可变性并避免饥饿
在这里插入图片描述

定义

  • 当队列为空时,从队列中获取元素的操作将会被阻塞(当试图从空的队列中获取元素的线程将会被阻塞,直到其它线程往空的队列插入新的元素)

  • 当队列为满时,从队列中添加元素的操作将会被阻塞(当试图向已满的队列中添加新元素的线程将会被阻塞,直到其它线程从队列中移除 一个或者多个或者完全清空,使队列变得空闲起来并后续新增)

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检查element()peek()不可用不可用
抛出异常当阻塞队列满时,再往队列里 add 插入元素会抛出 IllegalStateException(“Queue full”);当阻塞队列为空时,再从队列里 remove 移除元素会抛出 NoSuchElementException
特殊值插入方法,成功返回 true,失败返回 false;移除方法,成功返回出队列的元素,队列里没有就返回 null
一直阻塞当阻塞队列满时,生产者线程继续往队列里 put 元素,队列会一直阻塞生产者线程直到 put 数据或者响应中断退出;当队列为空时,消费者线程视图从队列里 take 元素,队列会一直阻塞消费者直到队列可用
超时退出当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出

操作分解

添加元素

假如现在队列里有一个元素,第一个元素也叫头节点
在这里插入图片描述
然后我们添加一个元素,当我们添加一个元素时,那么这个新添加的元素就是尾节点了(头节点指向尾节点)
在这里插入图片描述
同样地,再添加一个元素也是一样
在这里插入图片描述

移除元素

在上图中移除一个元素是移除最先添加的元素,也就是 11
在这里插入图片描述
从上图中再移除一个元素,也就是 12
在这里插入图片描述

动画演示

队列遵循的是先进先出原则
在这里插入图片描述

ArrayBlockingQueue

在这里插入图片描述

抛出异常

IllegalStateException

那么这个抛出异常怎么理解呢?可以来一段代码测试一下

package juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @author Woo_home
 * @create by 2020/3/14
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
    	// 创建一个 ArrayBlockingQueue 并初始化大小为 3
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        // 往队列里面添加 3 个元素,add 成功会返回 true
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
    }
}

输出:
在这里插入图片描述
因为我们已经将 ArrayBlockingQueue 初始化为 3,所以添加 3 个元素的时候是可以的,但是当我们在添加一个元素的时,就会抛出一个异常,如下:

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));

		// 再次添加一个元素
        System.out.println(queue.add("x"));
    }
}

抛出异常了,异常为 Queue full(队列已经满了)
在这里插入图片描述
而 ArrayBlockingQueue 的 add(E e) 方法的源码如下:

可以发现当我们使用 add() 方法的时候使用的是 ArrayBlockingQueue 的 add() 方法
在这里插入图片描述
而 ArrayBlockingQueue 的 add 方法实际调用的是父类 AbstractQueue 的 add() 方法,AbstractQueue 的 add() 方法在每次添加元素时都会使用 offer 方法(该方法是实现 Queue 接口的方法)进行判断,判断此时的队列是否已经满了,如果没满,则添加元素并且返回 true,否则抛出 IllegalStateException(“Queue full”) 异常

add(E e)

在这里插入图片描述

NoSuchElementException

package juc;

/**
 * @author Woo_home
 * @create by 2020/3/14
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.add("a");
        queue.add("b");
        queue.add("c");

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

这个程序往队列里面添加添加三个元素,然后取出三个元素,上面已经讲过,队列是遵循先进先出原则,移除是移除最先添加的元素

输出:
在这里插入图片描述
这时的队列元素已经全部移除,已经为空了,当再次 remove 时则会抛出 NoSuchElementException 异常,如下:

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.add("a");
        queue.add("b");
        queue.add("c");

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());

		// 经过上面的 remove,队列此时已为空
        System.out.println(queue.remove());
    }
}

输出:
在这里插入图片描述
当队列为空时,在往队列里 remove 移除元素会抛出 NoSuchElementException 异常

特殊值

offer(E e)

看下下面这段代码

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        System.out.println(queue.offer("x"));
    }
}

输出:
在这里插入图片描述
offer 的操作跟 add 差不多,都是添加元素,但是 offer 方法添加元素当添加元素的数量大于初始化的值时会返回一个 false 而不是抛出异常

poll()

poll() 操作跟 remove() 操作差不多,都是移除元素,但是 poll 的移除是成功返回队列的元素,队列里没有的元素就返回 null 而不是抛出异常

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

输出:
在这里插入图片描述

阻塞

  • 当阻塞队里满时,生产者线程继续往队列里 put 元素,队列会一直阻塞生产者线程知道 put 数据 or 响应中断退出
  • 当阻塞队列为空时,消费者线程视图从队列里 take 元素,队列会一直阻塞消费者线程直到队列可用
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.put("a");
        queue.put("b");
        queue.put("c");

        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
    }
}

输出:
在这里插入图片描述

超时

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        // 如果队列满了超过 3 s,则返回 false
        System.out.println(queue.offer("a", 3L,TimeUnit.SECONDS));
    }
}

当使用 offer 超时函数时,如果队列已满,则超时等待设定的时间,如果时间过了还没有空位置的话,则返回 false
在这里插入图片描述

<think>嗯,用户之前让我介绍了JUC并发工具包,现在又问线程池,显然想更深入了解线程池的部分。首先,我需要回顾之前提到的线程池内容,确保这次回答更详细全面。 用户可能对线程池的基本概念有一定了解,但需要深入原理、参数配置、使用场景以及注意事项。他们可能在实际开发中遇到了性能问题,或者想优化现有的线程池配置。 我需要分结构讲解,比如核心参数、工作流程、常见类型、配置建议和注意事项。要避免使用过多专业术语,用更易懂的例子和场景说明。例如,解释corePoolSize和maxPoolSize时,可以用银行柜台的例子,让读者更容易理解。 另外,用户可能对如何选择线程池类型和配置参数有疑问,比如为什么推荐自定义ThreadPoolExecutor而不是Executors的工厂方法。需要强调资源耗尽的风险,比如OOM,并举出newFixedThreadPool和newCachedThreadPool的问题。 还要提到线程池的工作流程,任务队列的不同类型及其影响,比如LinkedBlockingQueue和SynchronousQueue的区别。可能用户在处理高并发任务时,对任务排队和拒绝策略不太清楚,需要详细说明四种拒绝策略及其适用场景。 在注意事项部分,需要提醒线程池的资源隔离、异常处理以及监控的重要性。特别是异常处理,如果不当可能导致任务静默失败,影响排查。可以给出使用UncaughtExceptionHandler或者Future.get()捕获异常的方法。 最后,总结部分要简明扼要,强调线程池的优势和正确使用的重要性,帮助用户形成系统化的理解。确保回答结构清晰,层次分明,让用户能逐步掌握线程池的核心知识点。</think>### 线程池深度解析:Java并发编程的发动机 线程池是 JUC 中资源管理的核心工具,通过复用线程降低创建/销毁开销,提升系统吞吐量。以下从底层原理到实践场景全面剖析: --- #### 一、线程池核心参数(以 `ThreadPoolExecutor` 为例) ```java new ThreadPoolExecutor( int corePoolSize, // 核心线程数(常驻线程) int maximumPoolSize, // 最大线程数(应急线程) long keepAliveTime, // 非核心线程空闲存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程创建工厂 RejectedExecutionHandler handler // 拒绝策略 ) ``` **参数比喻**: - 核心线程 = 银行固定柜台 - 最大线程 = 临时加开的流动柜台 - 任务队列 = 客户等候区 - 拒绝策略 = 客满时的处理方式 --- #### 二、线程池工作流程(关键执行逻辑) 1. 提交任务时优先使用核心线程 2. 核心线程满 → 任务进入队列 3. 队列满 → 创建非核心线程(不超过 maximumPoolSize) 4. 线程和队列均满 → 触发拒绝策略 **流程图**: ``` 新任务提交 → 核心线程是否满? ├─ 否 → 创建核心线程执行 └─ 是 → 队列是否满? ├─ 否 → 加入队列等待 └─ 是 → 能否创建新线程? ├─ 能 → 创建临时线程执行 └─ 不能 → 执行拒绝策略 ``` --- #### 三、四大拒绝策略对比 | 策略类 | 行为 | 适用场景 | |---------------------------------|--------------------------------------|-----------------------| | `AbortPolicy`(默认) | 抛出 `RejectedExecutionException` | 严格要求任务完整性 | | `CallerRunsPolicy` | 由提交任务的线程直接执行 | 流量削峰,降低提交速度 | | `DiscardPolicy` | 静默丢弃新任务 | 允许丢任务(如日志监控)| | `DiscardOldestPolicy` | 丢弃队列最旧任务,重试提交新任务 | 优先处理最新任务 | --- #### 四、常见线程池类型(通过 `Executors` 创建) 1. **固定线程池** ```java ExecutorService fixedPool = Executors.newFixedThreadPool(5); ``` - 特点:corePoolSize = maximumPoolSize,队列无界 - 风险:任务堆积可能导致 OOM 2. **缓存线程池** ```java ExecutorService cachedPool = Executors.newCachedThreadPool(); ``` - 特点:corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,队列为 `SynchronousQueue` - 风险:线程数量失控 3. **单线程池** ```java ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); ``` - 特点:保证任务顺序执行,队列无界 - 风险:同固定线程池 4. **定时线程池** ```java ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3); scheduledPool.scheduleAtFixedRate(task, initialDelay, period, unit); ``` - 支持定时/周期性任务 --- #### 五、线程池配置黄金法则 1. **CPU 密集型任务** ```math corePoolSize = CPU核心数 + 1 ``` (如 8 核 CPU → 9 线程) 2. **IO 密集型任务** ```math corePoolSize = CPU核心数 × 2 ``` (如 8 核 CPU → 16 线程) 3. **混合型任务** - 拆分为 CPU 密集和 IO 密集两部分 - 分别使用不同线程池 --- #### 六、生产环境最佳实践 1. **禁止使用 `Executors` 创建线程池** - 推荐手动配置 `ThreadPoolExecutor` 参数 ```java // 标准安全配置示例 new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), // 核心线程数 Runtime.getRuntime().availableProcessors() * 2, // 最大线程数 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), // 有界队列 new CustomThreadFactory(), // 自定义线程命名 new ThreadPoolExecutor.CallerRunsPolicy() // 降级策略 ); ``` 2. **监控关键指标** ```java ThreadPoolExecutor pool = (ThreadPoolExecutor) executor; System.out.println("活跃线程数:" + pool.getActiveCount()); System.out.println("已完成任务数:" + pool.getCompletedTaskCount()); System.out.println("队列大小:" + pool.getQueue().size()); ``` 3. **优雅关闭** ```java executor.shutdown(); // 停止接收新任务 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 强制终止 } ``` --- #### 七、常见问题与解决方案 1. **线程池打满导致服务不可用** - 增加队列容量 - 优化任务处理速度 - 使用熔断机制 2. **线程上下文切换开销大** - 降低线程数量 - 使用协程(需第三方库,如 Quasar) 3. **任务执行异常丢失** - 在任务内加 try-catch - 使用 `Future` 获取异常 ```java Future<?> future = executor.submit(task); try { future.get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); // 处理异常 } ``` --- ### 总结:线程池的三层认知 1. **基础层**:理解核心参数与工作流程 2. **实践层**:根据业务特点选择配置策略 3. **高级层**:监控调优与异常处理体系 **特别提示**:在高并发场景下,建议为不同业务类型使用独立线程池,避免相互影响(如订单处理与日志记录分开)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值