线程池
为什么使用线程池?
- Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来许多好处
- 降低资源消耗,通过重复利用已经创建好的线程省去线程创建销毁带来的额外开销
- 提高响应速度,使用线程池中已经创建好的线程直接处理响应,而不需要等待线程创建的过程
- 提高线程的管理性,线程是稀缺资源,每台机器所能够承载的线程并发数量是有限的,无限制的创建线程不仅浪费系统资源,还会降低系统的稳定性,使用线程池可以进行线程统一分配,调优和监控
线程池的各个类之间的继承层级关系
- 顶层接口:Executor接口,其中只定义了一个execute(Runnable command)方法,该方法用于执行已提交到线程池的Runnable对象
- 次级接口:ExecutorService接口,继承自Executor接口,在其基础上增加了一些更好的管理线程池的方法,如shutdown()、submit()方法,可以理解为真正的线程池接口
- 抽象类:AbstractExecutorService抽象类,实现了ExecutorService接口中的大部分方法
- 核心类:ThreadPoolExecutor类,线程池的核心实现类,使用详细参数构建线程池,执行被提交到线程池中的任务
- 此外,ScheduledExecutorService 接口继承了ExecutorService接口,提供了“带周期执行”功能的ExecutorService
- ScheduledThreadPoolExecutor是ScheduledExecutorService接口的一个实现类,可以在给定的延迟后运行命令,或者定期执行命令,它比Timer更灵活,功能更强大
线程池参数及其各个参数的意义和使用
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler han
)
corePoolSize
- 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于 corePoolSize
- 如果当前线程数为 corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行
- 如果执行了线程池的 prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程
maximumPoolSize
- 线程池中允许的最大线程数,如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize
keepAliveTime
- 线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于 corePoolSize 时才有用
unit
- keepAliveTime的时间单位
workQueue
- 用于保存等待执行的任务的阻塞队列,一般来说,我们应该尽量使用有界队列,因为使用无界队列作为工作队列会对线程池带来如下影响。
- workQueue 必须是 BlockingQueue 阻塞队列。当线程池中的线程数超过它的corePoolSize 的时候,线程会进入阻塞队列进行阻塞等待。通过 workQueue,线程池实现了阻塞功能
- 当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize。
由于 1,使用无界队列时 maximumPoolSize 将是一个无效参数。 - 由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数。
- 更重要的,使用无界 queue 可能会耗尽系统资源,有界队列则有助于防止资源耗尽,同时即使使用有界队列,也要尽量控制队列的大小在一个合适的范围
所以我们一般会使用,ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
threadFactory
- 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名,当然还可以更加自由的对线程做更多的设置,比如设置所有的线程为守护线程
RejectedExecutionHandler
- 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了 4 种策略:
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务
线程池的工作机制
1.如果当前运行的线程少于 corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)
2.如果运行的线程等于或多于 corePoolSize,则将任务加入 BlockingQueue
3.如果无法将任务加入 BlockingQueue(队列已满),则创建新的线程来处理任务
4.如果创建新线程将使当前运行的线程超出 maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution()方法
提交任务执行
- execute方法提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
- submit方法用于提交有返回值的任务,线程池会返回一个feature对象,通过它可以判断任务是否被执行成功,通过其中的get方法获取任务的返回值
关闭线程池
- 可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池
- 它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止
- shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
- shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true
如有错误请联系作者改正,谢谢!