ExecutorService使用
写在前面
学习多线程,有几个很关键的点,要提前预习
-
几个关键字
volatile
synchronized
final
static -
Thread基本方法
多线程编程方式是由 ExecutorService 发起操作的,其实操作的对象,还是基于线程,关于线程/job/task的颗粒度,以及这里ExecutorService中线程池的概念,都是要加强学习的
这里先对 ExecutorService 的几个方法,简介一下
一、线程基础 Thread
参照这里Thread的基本知识介绍。
一、基本方法介绍
1.1、shutdown()
- 不能接受新的submit
- 并没有任何的interrupt操作,会等待线程池中所有线程(执行中的以及排队的)执行完毕
可以理解为是个标识性质的方法,标识这程序有意愿在此刻终止线程池的后续操作。
1.2、shutdownNow()
- 会尝试interrupt线程池中正在执行的线程
- 等待执行的线程也会被取消
- 但是并不能保证一定能成功的interrupt线程池中的线程。
- 会返回并未终止的线程列表List
shutdownNow()方法比shutdown()强硬了很多,不仅取消了排队的线程而且确实尝试终止当前正在执行的线程。
1.3、awaitTermination(n, TimeUnit)
- 该方法返回值为boolean类型
- 方法的两个参数规定了方法的阻塞时间,在阻塞时间内除非所有线程都执行完毕才会提前返回true
- 如果到了规定的时间,线程池中的线程并没有全部结束返回false
- InterruptedException 这个异常也会导致方法的终止
利用这个阻塞方法的特性,我们可以优雅的关闭线程池中的任务。
Java中在使用Executors线程池时,有时场景需要主线程等各子线程都运行完毕后再执行。这时候就需要用到ExecutorService接口中的awaitTermination方法.
比如应用场景为线程池的有效执行时间为20S,20S之后不管子任务有没有执行完毕,都要关闭线程池。代码如下:
ExecutorService es = Executors.newFixedThreadPool(10);
es.execute(new Thread());//执行子线程任务
try {
es.shutdown();
if(!es.awaitTermination(20,TimeUnit.SECONDS)){
//20S
System.out.println(" 到达指定时间,还有线程没执行完,不再等待,关闭线程池!");
es.shutdownNow();
}
} catch (Throwable e) {
// TODO Auto-generated catch block
es.shutdownNow();
e.printStackTrace();
}
与shutdown()方法结合使用时,尤其要注意的是shutdown()方法必须要在awaitTermination()方法之前调用,该方法才会生效。否则会造成死锁。
1.4、submit()
重载了三个submit方法,接收入参Runnable,Callable
1.5、invokeAll
任务的批量提交invokeAll(),两个重载
1.6、invokeAny
方法invokeAny,invokeAll都具有阻塞性。
invokeAny取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务。
invokeAll等线程任务执行完毕后,取得全部任务的结果值。
1.7、isTerminated()
若关闭后所有任务都已完成,则返回true。注意除非首先调用shutdown或shutdownNow,否则isTerminated永不为true。返回:若关闭后所有任务都已完成,则返回true。
二、操作使用
2.1、选择合适的线程池
有如下常见的几种线程池的构建,区分场景
newFixedThreadPool ----->
newWorkStealingPool -----> 使用场景:
newSingleThreadExecutor -----> 使用场景:
newCachedThreadPool -----> 使用场景:
newScheduledThreadPool ----->
new ThreadPoolExecutor(…) -----> 使用场景:自适应
2.2、newFixedThreadPool
定义:定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
使用场景:任务量比较固定但耗时长的任务
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.3、newWorkStealingPool
定义:工作窃取线程池,创建一个拥有多个任务队列(以便减少连接数)的线程池
使用场景:会创建一个含有足够多线程的线程池,来维持相应的并行级别,它会通过工作窃取的方式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行。
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
newWorkStealingPool虽是1.8才有的,但 ForkJoinPool 是1.7就有的对象。
2.4、newSingleThreadExecutor
定义:单线程池,只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
使用场景:多个任务顺序执行(FIFO,优先级)
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
装饰者模式
2.5、newCachedThreadPool
-
定义:缓冲线程池,一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
-
使用场景:任务量大但耗时少的任务
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2.6、newScheduledThreadPool
定义:调度线程池
使用场景:定时以及周期性执行任务
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
2.7、ThreadPoolExecutor
定义:自定义线程池
使用场景:自定义线程池,一般不需要
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
以上默认的几种都是 JDK本身基于自定义线程池ThreadPoolExecutor构造的应对不同场景的线程池,实际使用中,缓冲线程池和定长线程池用的较多(基本应付我们遇到的场景)