线程池核心参数
int corePoolSize:线程池当中所一直维护的线程数量,如果线程池处于任务空闲期间,那么该线程也并不会被回收掉
int maximumPoolSize:线程池中所维护的线程数的最大数量
long keepAliveTime:超过了corePoolSize的线程在经过keepAliveTime时间后如果一直处于空闲状态,那么超过的这部分线程将会被回收掉
TimeUnit unit:指的是keepAliveTime的时间单位
BlockingQueue workQueue:向线程池所提交的任务位于的阻塞队列,它的实现有多种方式
ThreadFactory threadFactory:线程工厂,用于创建新的线程并被线程池所管理,默认线程工厂所创建的线程都是用户线程且优先级为正常优先级
RejectedExecutionHandler handler:表示当线程池中的线程都在忙于执行任务且阻塞队列也已经满了的情况下,新到来的任务该如何被对待和处理。
线程池的总体执行策略
- 如果线程池中正在执行的线程数 < corePoolSize,那么线程池就会优先选择创建新的线程而非将提交的任务加到阻塞队列中。
- 如果线程池中正在执行的线程数 >= corePoolSize,那么线程池就会优先选择对提交的任务进行阻塞排队而非创建新的线程。
如果提交的任务无法加入到阻塞队列当中,那么线程池就会创建新的线程;如果创建的线程数超过了maximumPoolSize,那么拒绝策略就会起作用。
拒绝策略
AbortPolicy: 直接抛出一个运行期异常。
DiscardPolicy:默默地丢弃掉提交的任务,什么都不做且不抛出任何异常
DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务(队头元素),并且为当前所提交的任务留出一个队列中的空闲空间,以便将其放进到队列中
CallerRunsPolicy:直接由提交任务的线程来运行这个提交的任务关于线程池任务提交的总结
两种提交方式:提供了execute与submit两种方式来向线程池提交任务
- submit有三种方式,无论哪种方式,最终都是将传递进来的任务转换为一个Callable对象进行处理。submit方法是可以取代execute方法的,因为它既可以接收Callable任务,也可以接收Runnable任务。
当Callable对象构造完毕后,最终都会调用Executor接口中声明的execute方法进行统一的处理
在线程池中,最好将偏向锁的标记关闭。线程池状态
RUNNING:线程池可以接收新的任务提交,并且还可以正常处理阻塞队列中的任务。
- SHUTDOWN:不再接收新的任务提交,不过线程池可以继续处理阻塞队列中的任务。
- STOP:不再接收新的任务,同时还会丢弃阻塞队列中的既有任务;此外,它还会中断正在处理中的任务。
- TIDYING:所有的任务都执行完毕后(同时也涵盖了阻塞队列中的任务),当前线程池中的活动的线程数量降为0,将会调用terminated方法。
- TERMINATED:线程池的终止状态, 当terminated方法执行完毕后,线程池将会处于该状态之下。
RUNNING -> SHUTDOWN:当调用了线程池的shutdown方法时,或者当finalize方法被隐式调用后(该方法内部会调用shutdown方法)
RUNNING, SHUTDOWN -> STOP:当调用了线程池的shutdownNow方法时
SHUTDOWN -> TIDYING:在线程池与阻塞队列均变为空时
STOP -> TIDYING:在线程池变为空时
TIDYING -> TERMINATED:在terminated方法被执行完毕时
设置线程池参数
CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。
CPU密集型任务配置尽可能少的线程数量:
一般公式:CPU核数+1个线程的线程池
IO密集型
方法一:
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数2
方法二:
IO密集型,即该任务需要大量的IO,即大量的阻塞。
在单线程上运IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。
所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
IO密集型时,大部分线程都阻塞,故需要多配置线程数:
参考公式:CPU核数 /(1 - 阻系数)
比如8核CPU:8/(1 - 0.9)=80个线程数
阻塞系数在0.8~0.9之间
或者是:CPU 核数 [ 1 +(I/O 耗时 / CPU 耗时)]
方法三:
放入队列的操作,如果队列放入失败,线程池就会选择去创建线程了。因此,我们或许可以尝试自定义线程池,针对 offer 操作,做一些自定义处理。
也就是将任务放入队列时,先检查线程池的线程数是否小于最大线程数,如果是,则拒绝放入队列,否则,再尝试放入队列中。
Executors线程池工具类(常用方法)
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
SynchronousQueue 同步队列
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newScheduledThreadPool
DelayedWorkQueue
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
- newWorkStealingPool
ForkJoinPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
核心源码分析
- 线程池本身的状态:ctl的高3位来表示
- 线程池中所运行着的线程的数量:ctl的其余29位来表示
execute()
addWorker()
Worker继承AQS,实现Runnable
runWorker()
getTask()
shutdown 和 shutdownNow 的区别
shutdown 会等待线程池中的任务执行完成之后关闭线程池,而 shutdownNow 会给所有线程发送中断信号,中断任务执行,然后关闭线程池
shutdown 没有返回值,而 shutdownNow 会返回关闭前任务队列中未执行的任务集合(List)