创建线程会产生系统开销,并且每个线程会占用一定的内存等资源,同时线程的销毁也需要带来一定的压力。过多的线程还会带来由于上下文切换等等的性能损耗。
使用线程池的好处:
1)提高响应速度:通过复用线程可以消除线程创建销毁带来的延迟,提示响应速度
2)降低资源消耗线程池可以统筹内存和CPU的使用,避免资源使用不当,线程池会根据配置和任务数量灵活控制线程数量,不够就创建,多了就回收,避免线程过多导致内存溢出,过少导致资源浪费
3)提高线程可管理行线程池可以统一管理资源,统一进行分配、调优、监控。
一、线程池参数
1.1 线程池核心参数
1)corePoolSize
核心线程数。默认情况核心线程会一直存活。
2)maxPoolSize
最大线程数。决定线程池最多可以创建多少个线程
3)KeepAliveTime+时间单位
设置线程空闲时间,当线程空闲时间超过KeepAliveTime就会被销毁。allowCoreThreadTimeOut 参数可以决定核心线程若空闲时间过长,是否可以被回收
4)ThreadFactory
线程工厂。用来创建新线程,可以对线程的属性进行定制,比如线程group、线程名称、优先级等。
5)workQueue
存放任务的队列,即缓冲队列。
6)Handler
任务被拒绝时的策略
拒绝时机:
- 调用shutdown等方法关闭线程池后,此时如果再向线程池提交任务,则遭到拒绝
- 线程池没有能力处理任务时,即线程数达到了maxPoolSize,切workQueue已满
1.2 线程池拒绝策略
拒绝策略统一实现java.util.concurrent.RejectedExecutionHandler
java在ThreadPoolExecutor中提供了其4中实现:
- AbortPolicy 当需要拒接任务时,直接抛出RejectedExecutionException运行时异常
- DiscardPolicy 当需要拒绝任务时直接丢弃,也不会给任何通知,可能会造成数据丢失
- DiscardOldestPolicy 当需要拒绝任务时,丢弃任务队列中最长时间未被执行的任务,同样存在数据丢失风险
- CallerRunsPolicy 当需要拒绝任务时,则将此任务交给提交该任务的线程去执行。不会造成数据丢失,而且当线程池满负载时,新来的任务由提交任务的线程去执行,这样主线程因为执行任务而被占用,也就减缓了提交新任务的速度,而线程池也可再次期间执掉一部分任务,腾出空间,相当于给了线程池一个缓冲期,并且相当于有一个负向反馈。
1.3 线程池常用阻塞队列
- ArrayBlockingQueue
- LinkedBlockingQueue
- SynchronizedQueue
内部没有缓冲区 - DelayedWorkQueue
三、线程池运行过程
四、 常见线程池
4.1 FixedThreadPool
固定线程数,无界队列,由于无界队列,会OOM。适用于任务数量不均匀、对内存压力不敏感场景。
4.2 CachedThreadPool
由于maximumPoolSize为Integer,MAX_VALUE,所以最终创建的线程数量会达到操作系统的上限或者导致内存不足。适用于要求低延迟的短期任务场景
4.3 ScheduledThreadPool
适用于定时任务执行场景,支持固定频率和固定延时
4.4 SingleThreadExecutor
单线程线程池,适用于需要一部执行但需要保证任务顺序的场景
SingleThreadScheduledExecutor
ForkJoinPool
采用分治思想,将大任务分解成多个小任务处理,然后合并结果
//TODO ForkJoinPool 需要重点关注
五、线程池异常捕获机制
总而言之,使用execute方法提交任务时,异常可以通过线程的UncaughtExceptionHandler机制捕捉异常。
而通过submit方式则不会,因为submit方法会将传入的runnable封装为future,而future内部执行时,直接进行了try catch,因而异常不会被抛出去,因为线程池也就感知不到异常。
https://www.cnblogs.com/ncy1/articles/11629933.html
7.4 线程数量应该设置为多少
CPU密集型任务设置为CPU核心数的1~2倍
IO密集型任务公式:CPU核心数*(1+平均等待时间(I/O)/平均工作时间(cpu计算))