线程池
一、使用线程池的好处
1. 降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗
2. 提高响应速度
一个任务完成的时间T=创建线程的时间T1+线程执行任务的时间(包括线程同步的时间)T2+线程销毁的时间T3。线程池技术关注于缩短与调整T1T3的时间,提高程序的性能
3. 提高线程的可管理性
线程是稀有资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
4. 防止服务器过载,形成内存溢出,或者cpu耗尽
二、JDK中线程池相关接口与类的关系
1. Executor
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
是Executor框架的基础,将任务的提交与任务的执行分离开来
2. ExecutorService
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
继承Executor接口,增加了一些shutdown(),submit()方法,对Executor做了一些扩展
3. AbstractExecutorService
是一个实现了Executor接口大部分方法的抽象类
4. ThreadPoolExecutor
线程池的核心实现类,用来执行被提交的任务
5. ScheduledExecutorServicce
继承ExecutorService,提供了带周期性执行的功能
6. ScheduledThreadPoolExecutor
ScheduledExecutorService的实现类,提供了延迟运行和周期性运行的功能
三、创建线程池各个参数的含义
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1.corePoolSize
线程池的核心线程数
提交一个任务时,如果当前线程数小于corePoolSize,线程池会创建一个新线程执行任务,如果当前线程等于corePoolSize,继续提交任务会将任务保存在阻塞队列中,等待被执行,如果执行了prestartAllCoreThreads()方法,线程池将会提前启动所有的核心线程
2.maxinumPoolSize
线程池允许的最大线程数
如果当前阻塞队列满了,继续提交任务,如果同时当前线程数小于maxinumPoolSIze时会创建新的线程执行任务
3.keepAliveTime
线程空闲时的存活时间,默认情况下该参数只在线程数大于corePoolSize时才生效
4.TimeUnit
keepAliveTime的时间单位
5.workQueue
保存等待任务的阻塞队列
一般来说,我们会使用有界队列,例如:ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue
5.1. 线程池中使用无界队列会出现的问题
1.使用无界队列maxinumPoolSize将是一个无效参数
2.maxinumPoolSize无效,线程数将永远不会超过corePoolSize
3.由于第2点,KeepAliveTime参数也无效
4.使用无界队列可能会耗尽系统资源,即使使用有界队列也要合理设置队列的大小
6.threadFactory
创建线程的工厂,可以更灵活的设置线程
7.RejectedExecutionHandler
线程池的拒绝策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,就会采取一种策略来处理该任务。线程池提供了四种策略,一般我们会实现该接口,自定义拒绝策略
7.1. AbortPolicy
直接抛出异常(默认策略)
7.2. CallerRunsPolicy
用调用者所在的线程执行该任务
7.3. DiscardOldestPolicy
丢弃阻塞队列最靠前的任务,并执行当前任务
7.4. DiscardPolicy
直接丢弃该任务
四、线程池的工作机制
1.提交任务时,当前线程数小于corePoolSize,新启线程执行该任务
2.当前线程数大于等于corePoolSize,将任务保存到BlockingQueue
3.如果阻塞队列已满,且运行的线程线程数不超过maxinumPoolSize,创建新的线程执行任务
4.如果当前线程数大于等于maxinumPoolSize,则调用拒绝策略处理该任务
五、线程池的扩展
ThreadPoolExecutor为我们提供了类似AOP的两个方法供我们自己实现beforeExecute(),afterExecute(),每个任务执行前后都会分别执行这两个方法,在调用shutdown()后会调用terminated()
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("beforeExecute " + t.getName());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("beforeExecute");
}
@Override
protected void terminated() {
System.out.println("terminated");
}
六、提交任务
1. execute()
用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
2. submit()
用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过future对象判断任务是否执行成功,并且通过future对象的get()获取返回值,get()方法会阻塞当前线程直到任务完成,get(long timeout,TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,任务不一定在这个时间段内执行完成
七、关闭线程池
可以通过调用shutdown()和shutdownNow()来关闭线程池,原理是遍历线程池中的工作线程,依次调用interrupt()来中断线程,所以无法响应中断的任务无法终止
两者之间的区别:
shutdown:中断空闲的线程
shutdownNow:中断所有的线程
八、合理配置线程池线程数的大小
CPU核心数可以用Runtime.getRuntime().availableProcessors()获取
1. 高并发,任务执行时间短的业务
线程池线程数可以设置为cpu核心数+1,用来减少线程的上下文切换
2. 并发度不高,任务执行时间长
2.1 任务属于CPU密集型任务
线程池线程数可以设置为cpu核心数+1,用来减少线程的上下文切换
2.2 任务是IO密集型任务
设置较多线程,通常为:CPU核心数*2
3.并发高,任务执行时间长
这种情况不仅仅是设置线程池可以解决的,首先应该从架构上考虑,先采用一些方案减少服务器压力,把服务器并发度降下来,在参考2的配置,如(缓存,异步机制,集群…)
九、JDK中定义的线程池
1. FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
创建一个固定线程数的线程池
通过参数我们可知,corePoolSize和maximumPoolSize都被设置为nThreads,线程池数量固定,keepAliveTime设置为0,空闲的线程会立即被终止,阻塞队列为基于链表的LinkedBlockingQueue,容量为Integer.MAX_VALUE。
2. SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建一个单个线程的线程池,适用于需要顺序执行各个任务,并且任意时间最多只有一个线程运行
corePoolSize和maximumPoolSize设置为1,其他与FixedThreadPool相同
3. CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
创建一个根据需求创建新线程的,大小无界的线程池,适用于执行很多短期异步任务的小程序,或者负载较轻的服务器
由参数可知,corePoolSize为0,也就是说核心线程数为空,maximumPoolSize为Integer.MAX_VALUE,空闲线程存活时间为60s。阻塞队列使用SynchronousQueue,这是一个没有容量的阻塞队列
如果任务的提交速度大于线程的处理速度,那么CachedThreadPool就会一直创建新的线程,这样会耗尽系统的资源
4. WorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
通过fork/join创建一个工作窃取的线程池
5. ScheduledThreadPoolExecutor
通过工厂类Executors来创建,可以创建两种类型的ScheduledThreadPoolExecutor
- ScheduledThreadPoolExecutor
适用于需要多个线程执行周期任务 - SingleThreadScheduledExecutor
适用于需要单个线程执行周期任务,同时需要保证顺序的执行各个任务
十、线程池的状态
1. RUNNING
线程池的初始状态
任务数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
该状态可以接收新的任务,对添加的任务进行处理
2. SHUTDOWN
调用shutdown(),线程状态由RUNNING切换为SHUTDOWN
该状态不接收新任务,但能处理已添加的任务
3. STOP
调用shutdownNow(),线程状态由RUNNING切换为STOP
该状态不接受新任务,不处理已添加的任务,并且会中断已添加的任务
4. TIDYING
当线程池在SHUTDOWN状态下,阻塞队列为空且线程池中执行的任务也为空时,线程状态由SHUTDOWN切换为TIDYING。当线程池在STOP状态下时,线程池中执行的任务为空时,线程状态由STOP切换为TIDYING。总的来说,就是当所有的任务终止,ctl为0时,线程池状态切换为TIDYING。
该状态时,线程池会执行钩子方法terminated(),该方法默认为空,用户可以重载该方法进行相应的处理
5. TERMINATED
TIDYING状态执行完terminated()方法后,状态就会切换为TERMINATED
线程池彻底终止就是TERMINATED状态