什么是线程池?
线程池是一种管理一系列线程的资源池。当有任务需要处理时,线程池会直接从池中获取可用线程来执行任务。任务处理完后,线程不会被销毁,而是返回线程池,等待下一个任务的到来。
线程池的好处
- 减小资源消耗:通过重用线程,减少频繁创建和销毁线程的开销。
- 提高响应速度:任务提交后,无需等待线程创建,可以立即执行。
- 可管理性:实现对线程的统一管理,便于监控和调试。
如何创建线程池
1. 使用 ThreadPoolExecutor
推荐使用 ThreadPoolExecutor
构造函数来创建线程池。
2. 通过 Executors 工具类
虽然可以通过 Executors
类创建线程池,但不推荐使用默认配置,因为默认创建的线程池使用的是有界队列,可能导致请求堆积,从而引发 OOM(内存溢出)。
ThreadPoolExecutor 的构造函数参数
ThreadPoolExecutor
的构造函数有七个主要参数:
- corePoolSize:核心线程数,任务队列未达到容量时,最大可以同时运行的线程数量。
- maximumPoolSize:最大线程数,当任务队列已满时,可以同时运行的最大线程数量。
- workQueue:任务队列,当线程数达到核心数时,新的任务会被存放在此队列中。
- keepAliveTime:当线程池中的线程数量大于核心线程数时,非核心线程空闲后等待的时间,超过此时间会被回收。
- unit:keepAliveTime 的时间单位。
- threadFactory:用于创建新线程的工厂。
- handler:拒绝策略。
拒绝策略
当线程池无法接收新任务时,会根据以下拒绝策略进行处理:
- AbortPolicy:抛出
RejectedExecutionException
,拒绝新任务。 - CallerRunsPolicy:在调用线程中运行被拒绝的任务,可能导致主线程阻塞。
- DiscardPolicy:直接丢弃新任务。
- DiscardOldestPolicy:丢弃最早的未处理任务。
提交任务的方式
- execute():提交任务时,如果未捕获异常,线程会终止并创建新线程替代。
- submit():提交任务时,异常被封装在
Future
对象中,线程继续复用。submit()
提供更灵活的错误处理机制。
如何命名线程池
- 直接实现
ThreadFactory
接口。 - 使用
ThreadFactoryBuilder
的setNameFormat
方法。
如何设定线程池的大小
- CPU 密集型:使用
CPU 核心数 + 1
。 - IO 密集型:使用
2N
,即两倍的 CPU 核心数。
按优先级设计线程池
使用 PriorityBlockingQueue
作为任务队列,可以通过以下方式实现优先级:
- 任务实现
Comparable
接口,重写compareTo
方法。 - 创建
PriorityBlockingQueue
时传入Comparator
对象。
问题及解决方案
- OOM 问题:继承
PriorityBlockingQueue
,重写offer
方法,当元素达到一定数量时返回false
。 - 饥饿问题:设置等待时间,超时后移除并重新加入队列。
- 性能问题:使用可重入锁(ReentrantLock),但可能影响性能。
Future 类
Future
类是异步编程的典型应用。它允许我们在任务执行过程中获取结果,提供了更灵活的错误处理机制。
总结
Java 的线程池是一种高效的并发处理机制,通过合理的配置和管理,可以显著提高程序的性能和响应速度。理解线程池的构造、参数、拒绝策略及任务提交方式,对于开发高效的并发应用至关重要。