线程池是一种管理和重用线程资源的机制,是利用池化思想设置和管理多线程的工具。线程池维护一定数量的线程,当有任务需要时,就从中选择一个的线程用来执行任务,当使用完成后该线程就会被重新放回线程池中,通过这样循环使用的方式来节省创建线程和销毁线程的各项资源开销。
线程池
七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
- corePoolSize(核心线程数):
- 这是线程池的基本大小,即在没有任务需要执行时也会保持活动状态的线程数。即使线程空闲,除非设置了
allowCoreThreadTimeOut
为true
,否则核心线程不会被终止。核心线程在遇到新任务时会立即开始执行任务,即使此时队列为空。
- 这是线程池的基本大小,即在没有任务需要执行时也会保持活动状态的线程数。即使线程空闲,除非设置了
- maximumPoolSize(最大线程数):
- 线程池能容纳同时执行的最大线程数量,包括核心线程和非核心线程。当活动线程数达到这个值后,如果还有新的任务提交并且队列已满,就会根据拒绝策略来处理新任务。
- keepAliveTime(存活时间):
- 非核心线程闲置时等待新任务的最长时间。超过这个时间,非核心线程会被终止。如果
allowCoreThreadTimeOut
设置为true
,则此参数也适用于核心线程。
- 非核心线程闲置时等待新任务的最长时间。超过这个时间,非核心线程会被终止。如果
- unit(时间单位):
- 用来指定
keepAliveTime
参数的时间单位,如TimeUnit.MILLISECONDS
、TimeUnit.SECONDS
等。
- 用来指定
- workQueue(工作队列/任务队列):
- 用于保存等待执行的任务的阻塞队列。线程池的任务将先添加到此队列中,当线程空闲时会从队列中取出任务来执行。
- threadFactory(线程工厂):
- 用于创建新线程的工厂,可以用来设定线程的名称、优先级、是否为守护线程等属性。
- handler(拒绝策略):
- 当线程池和任务队列都达到了它们的最大容量,对于新提交的任务需要采取的一种处理策略。JDK内置的拒绝策略有:
AbortPolicy
:默认策略,抛出RejectedExecutionException
异常。CallerRunsPolicy
:由调用线程(提交任务的线程)直接执行该任务。DiscardPolicy
:直接丢弃任务,不执行也不抛出异常。DiscardOldestPolicy
:丢弃队列中最旧的任务,并尝试重新提交当前任务。
- 当线程池和任务队列都达到了它们的最大容量,对于新提交的任务需要采取的一种处理策略。JDK内置的拒绝策略有:
处理流程
阻塞队列
它在普通队列的基础上增加了阻塞操作的特性,当队列为空时,试图从队列中获取元素的线程会被阻塞,直到队列中有新的元素加入;反之,当队列已满时,尝试向队列中添加元素的线程也会被阻塞,直到队列中有空间可供添加。这种机制称为等待-唤醒机制,由Java的并发工具类库自动管理,简化了多线程编程的复杂度。
常见的队列类型有
LinkedBlockingQueue
,默认无界【可能会导致OOM】,支持有界。入队时生成节点0;头尾两把锁,粒度小。ArrayBlockingQueue
,有界队列。创建时就生成节点;一把锁,粒度大。SynchronousQueue
,直接传递,不存储元素。添加任务的操作必须等到另一个线程的移除操作,否则一直处于阻塞状态。
确定核心线程数
N为核心数
- I0密集型任务,般来说:冷文件读写、DB读写、网络请求等
核心线程数大小设置为2N+ 1,由于这类任务在等待IO操作完成时并不会持续占用CPU,可以设置更多的线程来提高系统的并发能力。另外,考虑阻塞系数(任务执行时间中阻塞所占的比例),可以使用更精细的公式核心线程数 ≈ CPU核心数 / (1 - 阻塞系数)
。(即阻塞越久,核心线程数越多) - CPU密集型任,一般来说:计算型代码、Bitmap转换、 Gson转换等
核心线程数大小设置为N+ 1,这样可以充分利用CPU资源,避免过多的上下文切换,包含一个额外线程以应对瞬时的负载波动。