线程池 ExecutorService 详细介绍以及注意点区别

本文介绍了Java中四种线程池的创建方式及其应用场景,并对比了ExecutorService接口中submit与execute方法的区别,最后还讨论了shutdown与shutdownNow方法的不同之处。
部署运行你感兴趣的模型镜像

线程池 ExecutorService 相信java开发都用到,这里做个简单笔记

一 Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

二、 ExecutorService 的submit() 与execute()区别
1、接收的参数不一样 submit()可以接受runnable无返回值和callable有返回值
execute()接受runnable 无返回值

2、submit有返回值,而execute没有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。

3、submit方便Exception处理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

import java.util.ArrayList;  
import java.util.List;  
import java.util.Random;  
import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  

public class ExecutorServiceTest {  
    public static void main(String[] args) {  
        ExecutorService executorService = Executors.newCachedThreadPool();  
        List<Future<String>> resultList = new ArrayList<Future<String>>();  

        // 创建10个任务并执行  
        for (int i = 0; i < 10; i++) {  
            // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中  
            Future<String> future = executorService.submit(new TaskWithResult(i));  
            // 将任务执行结果存储到List中  
            resultList.add(future);  
        }  
        executorService.shutdown();  

        // 遍历任务的结果  
        for (Future<String> fs : resultList) {  
            try {  
                System.out.println(fs.get()); // 打印各个线程(任务)执行的结果  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } catch (ExecutionException e) {  
                executorService.shutdownNow();  
                e.printStackTrace();  
                return;  
            }  
        }  
    }  
}  

class TaskWithResult implements Callable<String> {  
    private int id;  

    public TaskWithResult(int id) {  
        this.id = id;  
    }  

    /** 
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行。 
     *  
     * @return 
     * @throws Exception 
     */  
    public String call() throws Exception {  
        System.out.println("call()方法被自动调用,干活!!!             " + Thread.currentThread().getName());  
        if (new Random().nextBoolean())  
            throw new TaskException("Meet error in task." + Thread.currentThread().getName());  
        // 一个模拟耗时的操作  
        for (int i = 999999999; i > 0; i--)  
            ;  
        return "call()方法被自动调用,任务的结果是:" + id + "    " + Thread.currentThread().getName();  
    }  
}  

class TaskException extends Exception {  
    public TaskException(String message) {  
        super(message);  
    }  
}  

执行的结果类似于:

call()方法被自动调用,干活!!!             pool-1-thread-1  
call()方法被自动调用,干活!!!             pool-1-thread-2  
call()方法被自动调用,干活!!!             pool-1-thread-3  
call()方法被自动调用,干活!!!             pool-1-thread-5  
call()方法被自动调用,干活!!!             pool-1-thread-7  
call()方法被自动调用,干活!!!             pool-1-thread-4  
call()方法被自动调用,干活!!!             pool-1-thread-6  
call()方法被自动调用,干活!!!             pool-1-thread-7  
call()方法被自动调用,干活!!!             pool-1-thread-5  
call()方法被自动调用,干活!!!             pool-1-thread-8  
call()方法被自动调用,任务的结果是:0    pool-1-thread-1  
call()方法被自动调用,任务的结果是:1    pool-1-thread-2  
java.util.concurrent.ExecutionException: com.cicc.pts.TaskException: Meet error in task.pool-1-thread-3  
    at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222)  
    at java.util.concurrent.FutureTask.get(FutureTask.java:83)  
    at com.cicc.pts.ExecutorServiceTest.main(ExecutorServiceTest.java:29)  
Caused by: com.cicc.pts.TaskException: Meet error in task.pool-1-thread-3  
    at com.cicc.pts.TaskWithResult.call(ExecutorServiceTest.java:57)  
    at com.cicc.pts.TaskWithResult.call(ExecutorServiceTest.java:1)  
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)  
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)  
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)  
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)  
    at java.lang.Thread.run(Thread.java:619)  

可以看见一旦某个task出错,其它的task就停止执行。

三、shotdown() showdownNow()区别

可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。
shutdown() 方法在终止前允许执行以前提交的任务,
shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。关闭未使用的 ExecutorService 以允许回收其资源。
一般分两个阶段关闭 ExecutorService。第一阶段调用 shutdown 拒绝传入任务,然后调用 shutdownNow(如有必要)取消所有遗留的任务

// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
    threadPool.shutdown();

四、Runnable()与Callable()区别

如果是一个多线程协作程序,比如菲波拉切数列,1,1,2,3,5,8…使用多线程来计算。
但后者需要前者的结果,就需要用callable接口了。
callable用法和runnable一样,只不过调用的是call方法,该方法有一个泛型返回值类型,你可以任意指定。

runnable接口实现的没有返回值的并发编程。
这里写图片描述
callable实现的存在返回值的并发编程。(call的返回值String受泛型的影响) 使用Future获取返回值。
这里写图片描述

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think>我们正在讨论Java线程池ExecutorService,用户希望了解其使用、原理及最佳实践。根据之前的对话历史和提供的参考资料,我们可以系统地组织回答。 参考资料摘要: [^1]:ThreadPoolExecutor是线程池的核心实现类,Executors工厂类提供便捷创建方法。 [^2]:ThreadPoolExecutor和ScheduledThreadPoolExecutor都实现了Executor和ExecutorService接口。 [^3]:线程池中的线程用Worker对象封装,Worker是ThreadPoolExecutor的内部类,持有线程池引用。 [^4]:介绍了不同类型的线程池(Fixed, Cached, SingleThread, Scheduled, WorkStealing)及其使用场景。 回答结构: 1. ExecutorService概述:介绍其在Java并发包中的地位和作用。 2. 使用指南:如何创建(通过Executors工厂或直接构造ThreadPoolExecutor)、提交任务、关闭线程池。 3. 核心原理:重解释ThreadPoolExecutor的工作原理(任务调度、线程复用、队列管理、拒绝策略等)。 4. 最佳实践:参数配置、线程池选择、异常处理、监控等。 5. 常见问题与解决方案:如任务堆积、死锁、资源泄露等。 注意引用标注:在关键结论或数据后添加引用标识。 数学表达式:如线程池性能模型中的公式需要用$...$或$$...$$表示。 最后生成相关问题。 </think>### Java线程池`ExecutorService`的使用、原理及最佳实践 `ExecutorService`是Java并发包(`java.util.concurrent`)的核心接口,用于管理线程池的生命周期和任务执行。以下从使用、原理和实践三个维度展开: --- #### 一、使用指南 1. **创建线程池** 通过`Executors`工厂类或直接构造`ThreadPoolExecutor`实现: ```java // 工厂方法(推荐简单场景) ExecutorService fixedPool = Executors.newFixedThreadPool(10); // 固定大小线程池 [^4] ExecutorService cachedPool = Executors.newCachedThreadPool(); // 弹性线程池(需防OOM)[^4] ExecutorService singlePool = Executors.newSingleThreadExecutor(); // 单线程顺序执行 [^4] // 手动配置(推荐生产环境) ExecutorService customPool = new ThreadPoolExecutor( corePoolSize, // 核心线程数(常驻) maxPoolSize, // 最大线程数(队列满时扩容) keepAliveTime, // 非核心线程空闲存活时间 TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), // 任务队列 new CustomRejectedPolicy() // 自定义拒绝策略 ); ``` 2. **提交任务** - `execute(Runnable task)`: 提交无返回值的任务 - `submit(Callable<T> task)`: 提交有返回值的任务,返回`Future<T>`对象 ```java Future<Integer> future = customPool.submit(() -> { // 计算逻辑 return 42; }); Integer result = future.get(); // 阻塞获取结果 ``` 3. **关闭线程池** ```java customPool.shutdown(); // 优雅关闭:停止接收新任务,处理完队列任务 customPool.shutdownNow(); // 强制关闭:中断所有线程(返回未处理任务列表) ``` --- #### 二、核心原理 1. **任务调度流程** - 提交任务时,优先创建核心线程执行(若核心线程数未满) - 核心线程满载后,任务进入阻塞队列 - 队列满载后,创建非核心线程(不超过`maxPoolSize`) - 线程和队列均满载时,触发拒绝策略 [^1][^2] 2. **线程复用机制** 线程池通过`Worker`内部类封装线程(继承`AQS`锁)[^3]: ```java private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; // 实际工作线程 Runnable firstTask; // 初始任务 // 线程执行循环:从队列获取任务并执行 } ``` - 线程启动后循环执行`getTask()`从队列获取任务 - 空闲线程超时(`keepAliveTime`)后被回收 3. **生命周期状态** 通过原子变量`ctl`(32位)维护两种状态: - **高3位**:线程池状态(`RUNNING`, `SHUTDOWN`, `STOP`, `TIDYING`, `TERMINATED`) - **低29位**:有效线程数 状态转换关系: $$ \text{RUNNING} \xrightarrow{\text{shutdown()}} \text{SHUTDOWN} \xrightarrow{\text{队列空}} \text{TIDYING} \xrightarrow{\text{terminated()}} \text{TERMINATED} $$ [^2][^4] --- #### 三、最佳实践 1. **参数配置优化** - **核心公式**:最佳线程数 $N_{\text{threads}} = N_{\text{CPU}} \times U_{\text{CPU}} \times (1 + \frac{W}{C})$ - $N_{\text{CPU}}$:CPU核心数(`Runtime.getRuntime().availableProcessors()`) - $U_{\text{CPU}}$:目标CPU利用率(通常取0.8) - $W/C$:等待时间与计算时间的比值 - **队列容量**:根据业务峰谷值设置,避免`OOM`(如`ArrayBlockingQueue`有界队列)[^1] 2. **拒绝策略选择** | 策略 | 行为 | 适用场景 | |------|------|----------| | `AbortPolicy`(默认) | 抛出`RejectedExecutionException` | 需快速失败场景 | | `CallerRunsPolicy` | 由提交任务的线程执行 | 保证任务不丢失 | | `DiscardPolicy` | 静默丢弃任务 | 容忍数据丢失 | | `DiscardOldestPolicy` | 丢弃队列最旧任务并重试 | 时效性优先 | 3. **监控与调优** - 通过`ThreadPoolExecutor`方法监控: ```java pool.getActiveCount(); // 活动线程数 pool.getQueue().size(); // 队列积压量 ``` - **问题诊断**: - 线程饥饿:增大核心线程数或改用`SynchronousQueue` - CPU过高:降低线程数或优化任务逻辑 - OOM风险:使用有界队列并设置合理拒绝策略 [^4] 4. **场景化选型** | 线程池类型 | 特 | 适用场景 | |------------|------|----------| | `FixedThreadPool` | 固定线程数 | 负载稳定的服务(如Web后端) | | `CachedThreadPool` | 线程自动回收 | 短时异步任务(需防OOM) | | `ScheduledThreadPool` | 支持定时任务 | 心跳检测、定时报表 [^4] | | `WorkStealingPool` | 工作窃取负载均衡 | CPU密集型并行计算 [^4] | --- #### 四、常见问题解决方案 1. **任务堆积导致OOM** **方案**:使用有界队列 + `CallerRunsPolicy`拒绝策略,限制提交速率。 2. **线程泄漏** **方案**:确保任务正确处理异常(避免线程因异常终止),或使用`ThreadFactory`定制线程的`UncaughtExceptionHandler`。 3. **死锁** **方案**:避免任务间循环依赖;使用`newWorkStealingPool()`利用工作窃取算法打破僵局 [^4]。 4. **资源竞争** **方案**:对共享资源使用`ConcurrentHashMap`等并发容器;控制线程数不超过$2 \times N_{\text{CPU}}$。 > **关键原则**:根据业务场景(I/O密集型或CPU密集型)动态调整参数,并通过压测验证[^1][^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值