线程池状态
ThreadPoolExecutor 用高3位保存状态,低29位表示线程数量
RUNNING:可接受新任务
SHUTDOWN:不会接受新任务,但会处理完队列中的任务
STOP:暴力停止
TIDYING:任务都执行完毕,会执行钩子函数terminated(),可进行相应处理
TERMINATED:终止状态
线程池有哪些参数
1、核心线程数(corePoolSize):线程中的核心线程数量
2、最大线程数(maximumPoolSize):线程中允许存在的最大线程=核心线程+救急线程
3、救急线程的存活时间(keepAliveTime):救急线程存活时间,默认为60s
4、时间单位:TimeUnit unit,针对救急线程
5、阻塞队列(workQueue):存放任务
6、线程工厂(threadFactory):可以给线程起一个想要的名字
7、拒绝策略(Handler):对于超出数量的任务进行相应的处理。
线程池执行流程
1、线程池刚开始是没有线程的,当有任务时,才会创建线程;
2、当线程数量达到corePoolSize时,线程池再接收的任务会进入阻塞队列workQueue,直到有空闲的线程;
3、当队列采用的是有界队列,当任务超过队列大小时,会采用救急线程来处理超出队列的任务
4、如果线程达到了maximumPoolSize,仍有新任务加入队列,这时会采用拒绝策略进行处理;
5、当高峰过去后,救急线程的存活时间由keepAliveTime和unit来决定
6、拒绝策略:
AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
CallerRunsPolicy 让调用者运行任务
DiscardPolicy 放弃本次任务
DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
Netty 的实现,是创建一个新线程来执行任务
ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
newFixedThreadPool
特点
核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
阻塞队列是无界的,可以放任意数量的任务
适用于任务量已知,相对耗时的任务
newCachedThreadPool
特点
核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着全部都是救急线程(60s 后可以回收)
救急线程可以无限创建
队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况
newSingleThreadExecutor
使用场景:
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
任务调度线程池
在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
Fork/Join
Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型运算
所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解
Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率
Fork/Join 默认会创建与 cpu 核心数大小相同的线程池