1、线程池的三个好处:
- 降低资源消耗。可以重复利用已创建的线程,降低创建/销毁线程的开销
- 提高响应速度。
- 提高线程的可管理性。统一分配、调优、监控。
2、线程池的处理流程
新任务提交至线程池后的处理流程:
- 1.核心线程池是否已满,如果没满,则创建一个线程执行任务,如果满了,则进入下一个流程
- 2.判断队列是否已经满了,如果没满,则将任务存储在队列中,如果满了,则进入下一个流程
- 3.判断线程池是否已经满了,如果没满,则创建线程执行任务,如果满了,则按照策略处理无法执行的任务。
3、线程池的使用
3.1、线程池的创建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:线程池的基本大小
- maximumPoolSize:线程池的最大线程数
- keepAliveTime:线程的活动的保持时间。当线程空闲之后,保持存活的时间,如果任务量大,而任务的执行时间又短的话,可以增大线程池的存活时间,提高线程的利用率
- TimeUnit:线程活动保持时间的单位
- BlockingQueue:用于存放任务的阻塞队列,队列的选择可以参考java中的阻塞队列
- ThreadFactory:用于设置创建线程的工厂。
- RejectedExecutionHandler:饱和策略。即当线程和队列都已经满了的时候,应该采取什么样的策略来处理新提交的任务,默认策略是AbortPolicy(抛出异常),其他的策略还有:CallerRunsPolicy(只用调用者所在线程来运行任务),DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务),DiscardPolicy(不处理,丢弃掉)
3.2、向线程池提交任务
提交任务有两种方法,分别是execute()和submit()。
execute()方法适用于任务提交之后没有返回值的这种情况,因为没有返回值,所以提交任务之后我们也无法判断任务是否执行成功。
submit方法适用于提交任务之后有返回值的情况,它会返回一个Future类型的对象,可以通过future.get()方法来获取返回值,get()会阻塞线程直到任务完成。
3.3、关闭线程池
关闭线程池有两种方法,分别是shutdown()和shutdownNow(),它们的原理都是遍历线程池中所有的线程,分别调用每个线程池的interrupt()方法来中断线程。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
3.4、合理的配置线程池
线程池配置的分析角度
- 任务的性质:IO密集型还是CPU密集型,cup密集型可以配置小一点的线程数,io密集型可以配置多的线程数
- 任务的优先级:可以用PriorityBlockingQueue队列来处理
- 任务处理时间的长短
- 任务的依赖性:比如说是否依赖数据库连接
线程池的任务队列的选择建议使用有界队列,因为如果任务太多,有界队列可以抛出异常便于我们排查,而无界队列会使队列中的任务越来越多,可能导致撑满内存,导致整个系统的不可用。