Java线程池详解
简介
什么是线程池
线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。常见的运用池化思想的有:内存池、数据库连接池。使用线程池的优点如下:
- 提高线程的利用率
- 提高程序的相应速度
- 便于统一管理线程对象
银行营业厅案例
假设银行正常可以同时给3个客户办理业务(绿色表示),最多可同时给5个用户办理业务(多余的用红色表示),等候区最多可以等待4个客户(用蓝色表示),小人表示客户。正常的营业厅处理业务流程如下图所示:
注:这个流程用于模拟线程池,和实际银行办理业务还是有点区别。
假设同时进来7个用户,办理业务效果如下:
4个等待区用户只有等前面的窗口办理完才能依次办理。
假设同时进来10个用户,办理业务效果如下:
第10个用户超出最大限度被拒绝办理业务,其余等待区用户只有等前面的窗口办理完才能依次办理,4和5号窗口超时后会重新进入空闲状态。
执行流程
上面案例中的正常办理业务窗口数对应线程池中的核心线程数,最多办理业务窗口数对应线程池中的最大线程数,等候区对应线程池中的阻塞(等待)队列。线程池关键节点的执行流程如下:
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务。
线程池的执行流程如下图所示:
创建方式
所有创建方式
Java线程池一共有7种,按创建类分为两种:
- 通过Executors类创建
- Executors.newFixedThreadPool
创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待 - Executors.newCachedThreadPool
创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程 - Executors.newSingleThreadExecutor
创建单个线程数的线程池,它可以保证先进先出的执行顺序 - Executors.newScheduledThreadPool
创建一个可以执行延迟任务的线程池 - Executors.newSingleThreadScheduledExecutor
创建一个单线程的可以执行延迟任务的线程池 - Executors.newWorkStealingPool
创建一个抢占式执行的线程池(任务执行顺序不确定)JDK 1.8 中添加
- Executors.newFixedThreadPool
- 通过ThreadPoolExecutor类创建
最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
一般使用Java提供了创建线程池的接口Executor(),推荐用子类ThreadPoolExecutor来创建线程池。这在阿里巴巴《Java开发手册》中有说明:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
通过ThreadPoolExecutor创建
该类参数最多的构造方法如下:
1 | public ThreadPoolExecutor(int corePoolSize, |
参数说明如下:
- corePoolSize
核心线程数 - maximumPoolSize
最大线程数 - keepAliveTime
最大线程数可以存活的时间,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间 - unit
计算keepAliveTime的单位 - workQueue
阻塞(等待)队列。一共有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等7种阻塞队列 - threadFactory
创建线程的工厂,主要用来创建线程,默认为正常优先级、非守护线程。 - handler
拒绝策略。一共有下面四种:- AbortPolicy
不执行新任务,直接抛出异常,提示线程池已满 - DisCardPolicy
不执行新任务,也不抛出异常 - DisCardOldSetPolicy
将消息队列中的第一个任务替换为当前新进来的任务执行 - CallerRunsPolicy
直接调用execute来执行当前任务
- AbortPolicy
示例代码:
1 | public class ThreadPoolTest { |
运行结果:
1 | pool-1-thread-2 --->办理业务 |
当for循环中的i<10时运行结果:
1 | pool-1-thread-2 --->办理业务 |
可以发现只有9(最大+阻塞)个任务执行了,之后进来的任务会被拒绝。