不知道你是否还记得阿里巴巴的java代码规范中对多线程有这样一条强制规范:
【强制】线程资源必须通过线程池提供,不允许在程序中显示创建线程。
说明:使用线程池的好处是减少在创建和销毁线程池上所消耗的时间以及系统资源的开销,解决资源不足的问题,如果不适用线程池, 有可能造成系统创建大量同类线程而导致消耗完内存或者“过渡切换”的问题。
这条强制性规范也说明了使用线程池主要是解决一下两个问题:
(1)提升性能:线程池能独立负责线程的创建、维护和分配。在执行大量一部任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大限度地对已经创建的线程进行复用,使得性能提升明显。
(2)线程管理: 每个java线程池会保持一些基本的线程统计信息, 以便对线程进行有效管理,使得能对所接收到异步任务进行高效调度。
JUC 是java.util.concurrent工具包的简称, 是从JDK1.5开始加入JDK的,用于完成高并发、处理多线程的一个工具包。 JUC 的线程架构如下图所示:
Executor是java 异步目标任务的“执行者”接口,其目的是执行目标任务。
ExecutorService继承于Executor。 它是java异步目标任务的“执行者服务接口”,对外提供异步任务的接收服务。
AbstractExecutorService 是一个抽象类,它的目的是为ExecutorService 中的接口提供默认实现。
ThreadPoolExecutor 是线程池的实现类, 是JUC线程池的核心实现类。
ScheduledExecutorService 是一个接口,可以完成延时和周期性任务调度线程池的接口,其功能类似Timer/TimerTask、
ScheduledThreadPoolExecutor提供了ScheduledExecutorService 线程池接口中“延迟执行”和周期执行等抽象调度方法的具体实现。在高并发程序中, ScheduledThreadPoolExecutor 的性能由于Timer。
Executors 是一个静态工厂类,它通过静态工厂方法返回ExecutorService、ScheduledExecutorService等线程池示例对象。 java通过Executors 工厂类可以提供4种快捷创建线程池的方法。如下图所示:
但是在阿里巴巴的java代码规范中,有这样一条强制规范:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor 的方式。
说明: Executors返回的线程池对象的弊端如下:
(1)FixedThreadPool 和SingleThreadPool 运行的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
(2)CacheThreadPool 和ScheduledThreadPool 允许的创建线程数量为Integer.MAX_VALUE,可能 会创建大量的线程,从而导致OOM。
那么我们使用ThreadPoolExecutor 如何创建线程池呢,
我们通过标准构造器ThreadPoolExecutor去构造工作线程池,Executors工厂类中创建线程池的快捷工厂方法实际上是调用TheadPoolExecutor(定时任务使用ScheduledThreadPoolExecutor)线程池的构造方法完成的, 构造方法如下:
其中线程池执行器会根据corePoolSize和maximumPoolSize自动维护线程池中的工作线程,规则如下:
向线程池提交任务的两种方式分别是execute()方法和submit()方法.
execute() 方法:
submit()方法:
从接口可以得出submit()和executor()方法的区别是:
线程池的任务调度流程如下图所示:
在创建线程池的是,如果线程池的参数配置不合理,就会出现任务不能被正常调度的问题.
总结:
核心和最大线程数量,BlockingQueue队列等参数如果配置得不合理,可能会造成异步任务得不到预期的并发执行, 造成严重的排队等待现象.
想城池的调度器创建线程的一条重要的规则:在核心线程数已满后,还需要等待阻塞队列已满,才会去创建新的线程.
当线程池的任务缓存队列为有界队列且阻塞队列已满的情况,提交任务到线程池的时候就会被拒绝, 当线程池已经被关闭了, 提交任务到线程池也会被拒绝. 无论是哪一种情况任务被拒绝, 线程池都会调用RejectedExecutionHandler拒绝策略接口的实例的rejectedExecution方法, 拒绝策略的实现有以下5中:
一般情况下, 线程池启动后建议手动关闭,可以结合shutdown(),shutdownNow(),awaitTermination() 三个方法优雅地关闭一个线程池, 关闭步骤如下:
(1) 执行shutdow()方法,拒绝新任务的提交,并等待所有任务有序地执行完毕.
(2) 执行awaitTermination(Long timeout,TimeUnit unit)方法,指定超时时间,判断是否已经关闭所有任务,线程池关闭完成.
(3)如果awaitTermination()方法返回false,或者被中断,就调用shutDownNow()方法立即关闭线程池所有任务.
(4)补充执行awaitTermination(Long timeout,TimeUnit unit)方法,判断线程池是否关闭完成. 如果超时,就可以进入循环关闭,循环一定的次数,不断关闭线程池,直到其关闭或者循环结束.
关闭线程池代码如下:
public static void shutdownThreadPoolGrecefully(ExecutorService threadPool){
if (!(threadPool instanceof ExecutorService)|| threadPool.isTerminated()){
return;
}
try {
//拒绝接收新任务
threadPool.shutdown();
}catch (SecurityException e){
return;
}catch (NullPointerException e){
return;
}
try {
//等待60s,等待线程池中的任务完成执行
if(!threadPool.awaitTermination(60, TimeUnit.SECONDS)){
//调用shutdowNow 取消正在执行的任务
threadPool.shutdownNow();
//再次等待60s ,如果还未结束,可以再次尝试,或则直接放弃
if(!threadPool.awaitTermination(60,TimeUnit.SECONDS)){
System.out.println("线程池任务未正常完成执行结束");
}
}
} catch (InterruptedException e) {
// 捕获异常,重新调用shutdowNow
threadPool.shutdownNow();
}
// 仍然没有关闭,循环关闭1000次,每次等嗲10毫秒
if (!threadPool.isTerminated()){
try {
for (int i=0;i<1000;i++){
if (threadPool.awaitTermination(10,TimeUnit.SECONDS)){
break;
}
threadPool.shutdownNow();
}
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}catch (Throwable e){
System.out.println(e.getMessage());
}
}
}
除了以上的关闭线程池方法以外,还可以在JVM中注册一个钩子函数,在JVM进程关闭之前,由钩子函数自动将线程池关闭,以确保资源正常释放,代码如下:
//懒汉式单例创建线程池:用于执行定时/顺序任务
static class SeqOrScheduledTargetThreadPoolLazyHolder{
//线程池:用于定时任务/顺序排队执行任务
static final ScheduledThreadPoolExecutor EXECUTOR= new ScheduledThreadPoolExecutor(1,new CustomThreadFactory("seq"));
static {
//注册JVM关闭时的钩子函数
Runtime.getRuntime().addShutdownHook(
new ShutdownHookThread("定时和顺序任务线程池",
new Callable(){
@Override
public Void call() throws Exception {
//关闭线程池
shutdownThreadPoolGrecefully(EXECUTOR);
return null;
}
}
));
}
}
到此为止,线程池的原理您明白了吗?