深入理解Java线程池
什么叫线程池
一个线程集合, 统一管理线程的数量, 线程的生命周期, 并尽可能重用池中的线程. 我们常用的数据库连接池也是相同思想的产物.
为什么使用
- 一个线程在创建和销毁时, 非常消耗资源. 为了尽可以减少线程创建和销毁的次数, 多次重用同一线程, 将资源消耗分摊到多个任务上.
- 不使用线程池管理线程, 若运行的线程数量过大, 可能会导致系统资源消耗过大, 出现异常.
如何使用
- 使用如下方式创建线程池:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) //静态包装后的常用线程池方法 Executors.newSingleThreadExecutor(); Executors.newFixedThreadExecutor(int nthreads); Executors.newCachedThreadExecutor();
- 静态方法含义
- Executors.newSingleThreadExecutor(): 创建一个线程池, 但只包含一个线程, 单线程执行所有任务, 若该线程异常结束, 重启一个新线程.
- Executors.newFixedThreadExecutor(int nthreads): 创建一个线程池, 添加一个任务创建一个线程, 直到线程数量等于最大线程数, 线程数则保持不变. 若有线程异常结束, 则重启一个新线程.
- Executors.newCachedThreadExecutor(): 创建一个线程池, 线程数量由jvm和执行的任务量决定, 任务增加时, 自动创建, 线程空闲超时(60秒)后, 自动回收.
深入参数分析
- 线程池的接口/类继承结构:
ThreadPoolExecutor -> AbstractExecutorService -> ExecutorService -> Executor
Executors 是ThreadPoolExecutor工厂方法实现. - ThreadPoolExecutor 完整签名 参数讲解:
- corePoolSize: 线程池中所保存的线程数量(包括空闲线程)
- maximumPoolSize: 线程池中所允许的最大线程
- keepAliveTime: 当线程数大于核心数时, 销毁线程的超时时间
- unit: keepAliveTime 的时间单位
- workQueue: 保存待执行的任务的任务队列
- threadFactory: 执行创建线程时所使用的工厂类
- handler: 拒绝执行的任务的处理类
线程池线程创建逻辑
- 当运行线程数 < corePoolSize 时: 直接添加新线程执行任务
- 当任务队列满, 运行线程数 >= corePoolSize且 < maximumPoolSize 时: 创建新线程执行任务.
- 当任务队列满, 运行线程数 >= corePoolSize且 >= maximumPoolSize 时: 执行拒绝策略
线程池线程超时逻辑
- 当线程池中线程保持 keepAliveTime 时间后, 自动销毁, 超时时间的单位由 unit 决定.
任务队列的类型
- ArrayBlockQueue: 有界队列, 可以防止资源耗尽, 但是难以调优, 不推荐使用.
- LinkedBlockQueue: 无界队列, 任务之间单独执行, 不相互影响.
- SynchronousQueue: 直接提交队列, 不保存任务, 直接提交线程. 但是每次插入必须等待另一个线程移除操作, 否则一直阻塞.
拒绝任务执行策略
- CallerRunsPolicy: 直接调用线程运行该任务. 该策略觉得该任务还可以再抢救. 一般会使用执行该execute的线程执行任务
- AbortPolicy: 丢弃任务, 抛出异常
- DiscardPolicy: 直接丢弃任务, 但是不抛出异常
- DiscardOldestPolicy: 将队列首部的旧任务剔除, 执行该任务. 请小心使用该策略.
静态方法的默认实现及输出演示:
Executors.newSingleThreadExecutor() : 内部调用 new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
()); ExecutorService executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 4; i++) { final int index = i; executor.execute(() -> { System.out.print(Integer.toString(index) + Thread.currentThread().getId() + " "); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); // 输出: 013 113 213 313 // 说明结果是同一线程, 顺序执行的
Executors.newFixedThreadExecutor(int nthreads) : 内部调用 new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
()); ExecutorService executor = Executors.newFixedThreadPool(2); for (int i = 0; i < 4; i++) { final int index = i; executor.execute(() -> { System.out.print(Integer.toString(index) + Thread.currentThread().getId() + " "); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); // 输出: 011 112 211 312 // 说明结果是两个线程, 独立顺序执行的
Executors.newCachedThreadExecutor() : 内部调用 new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
()); ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 4; i++) { final int index = i; executor.execute(() -> { System.out.print(Integer.toString(index) + Thread.currentThread().getId() + " "); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); // 输出: 011 112 213 314 // 说明结果是多个线程, 独立顺序执行的
如何配置线程池大小
- cpu密集型任务: 参考值 n(cpu数量) + 1
- io密集型任务 : 参考值 2 * n(cpu数量)
- 参数敏感时需要根据实际情况进行测试, 再具体调整参数
总结
- 推荐使用系统自带的 newSingleThreadExecutor(), newFixedThreadExecutor(), newCachedThreadExecutor() 三个创建线程池的静态方法创建线程, 能满足绝大部分情况.
- 自定义参数实现:
- 若对执行时间和顺序有要求, 建议使用优先级队列.
- 若任务较多可能超过系统限制, 建议使用有界队列.
- 实际情况建议多测试, 具体调整参数.
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 zhao4xi@126.com
文章标题:深入理解Java线程池
文章字数:1.2k
本文作者:Zhaoxi
发布时间:2018-12-15, 15:00:51
最后更新:2019-09-21, 15:05:05
原始链接:http://zhao4xi.github.io/2018/12/15/深入理解Java线程池/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。