1 什么是线程池
线程池其实就是一种多线程处理形式,把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。
2 为什么使用线程池
2.1 场景
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println("run thread->" + Thread.currentThread().getName());
userService.updateUserStatus(....)
}).start();
}
业务场景就是我们想使用多线程异步的方式去处理某些事情,以提高处理效率。
这段代码是存在问题的
1)创建销毁线程资源消耗; 我们使用线程的目的本是出于效率考虑,可以为了创建这些线程却消耗了额外的时间,资源,对于线程的销毁同样需要系统资源。
2)cpu资源有限,上述代码创建线程过多,造成有的任务不能即时完成,响应时间过长。
3)线程无法管理,无节制地创建线程对于有限的资源来说似乎成了“得不偿失”的一种作用。
采用线程池来管理可以解决这些问题
2.2 线程池的作用
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;
- 线程和任务分离,提升线程重用性(线程池分为线程集合和任务队列两部分)
- 控制线程并发数量,降低服务器压力,统一管理所有线程;
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3 Executor框架
3.1 简介
线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。
线程用于执行异步任务,单个的线程既是工作单元也是执行机制。
从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。
Executor框架就是线程池的实现。
3.2 Executor框架结构
Executor框架包括3大部分
(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
示例:
class callableTest implements Callable<String >{
@Override
public String call() {
try{
String a = "return String";
return a;
}
catch(Exception e){
e.printStackTrace();
return "exception";
}
}
}
(2)任务的执行。也就是把任务分派给多个线程的执行机制Executor。
示例:
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,
100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
Future<String> future = tpe.submit(new callableTest());
(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。
示例:
try{
System.out.println(future.get());
}
catch(Exception e){
e.printStackTrace();
}
finally{
tpe.shutdown();
}
3.3 Executor类图
3.4 Executor与ExecutorService源码对比
package java.util.concurrent;
/**
* 1.该接口提交任务,使得提交任务与任务处理隔离,不显示创建线程;
* 2.接受一个Runnable接口对象,并没有返回结果
*/
public interface Executor {
/**
* 提交任务,使得线程池中的线程处理任务逻辑
* 注意:该方法并没有返回任务的处理结果
* @param command Runnable任务
* @throws RejectedExecutionException 线程池拒绝异常
* @throws NullPointerException 空指针异常
*/
void execute(Runnable command);
}
package java.util.concurrent;
import java.util.List;
import java.util.Collection;
/**
* 1. 提供管理终止的方法,这些方法可跟踪异步线程执行任务的进度
* 2. 关闭线程池有两种方法:shutdown()、shutdownNow()
*/
public interface ExecutorService extends Executor {
/**
* 关闭线程池
* 1. 拒绝新的任务提交
* 2. 正在执行的任务完成后关闭
*/
void shutdown();
/**
* 立刻关闭线程池
* 1. 拒绝新的任务提交
* 2. 终止等待执行的任务,返回任务
* 3. 终止正在执行的任务,返回任务
*/
List<Runnable> shutdownNow();
// 判定线程池是否关闭
boolean isShutdown();
// shutdown()后所有任务是否终止
boolean isTerminated();
// shutdown()后所有任务在timeout后终止
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;
}
ExecutorService基础Executor,对它进一步做了扩展。能够接受任务处理结果。提供了一些控制线程的方法。
3.5 ExecutorService的三种实现
ThreadPoolExecutor:标准线程池
ScheduledThreadPoolExecutor:延迟任务线程池
ForkJoinPool:work-steeling模式线程池。池中的每个线程都创建一个队列,从而使用work-steeling算法(任务窃取)使得任务处理完成的线程从其他忙的线程队列中窃取任务
3.6 ThreadPoolExecutor
标准线程池
3.6.1 构造方法
构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize,// 最大线程数
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制,也叫拒绝策略
)
{ ... }
3.6.2 参数说明
1)核心线程数(corePoolSize)
核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;
2)任务队列长度(workQueue)
任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
3)最大线程数(maximumPoolSize)
最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;
4)最大空闲时间(keepAliveTime)
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;
3.6.3 饱和处理机制说明
ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:
1)AbortPolicy():丢弃任务并抛出 RejectedExecutionException 异常(默认)。
2)DiscardPolicy():丢弃掉该任务但是不抛出异常,不推荐这种(导致使用者没觉察情况发生)
3)DiscardOldestPolicy():丢弃队列中等待最久的任务,然后把当前任务加入队列中。
4)CallerRunsPolicy():那个线程调用的添加任务,就由那个线程执行
3.6.4 线程池工作流程图解
3.7 ScheduledThreadPoolExecutor
它继承了ThreadPoolExecutor,添加了延迟运行定期执行任务的能
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,用来延迟或定期执行任务。它比Timer更强大。Timer对应的是单个后台线程,ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
以上代码所示, keepAliveTime=0L,空闲线程会立即被终止。使用DelayedWorkQueue阻塞队列存储任务java.util.concurrent.ScheduledThreadPoolExecutor.ScheduledFutureTask,具有延时和周期执行任务。
ScheduledFutureTask类主要有三个变量,如下。注意time相同时,按sequenceNumber小的优先出队。
sequenceNumber:添加到阻塞队列的序列号
time:任务执行的绝对时间
period:执行任务的间隔周期
如下步骤是ScheduledFutureTask的任务执行步骤:
1)线程获取可以出队的任务(ScheduledFutureTask的time > 当前时间,说明当前任务可以出队);
2)线程执行出队任务;
3)线程修改出队任务的time变量(下次被执行的时间);
4)完成任务后,放回队列。
3.8 ForkJoinPool
ForkJoinPool分支/合并框架,就是在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不能再拆为止),再将一个个的小任务运算的结果进行Join汇总
它和ThreadPoolExecutor的区别是,ThreadPoolExecutor只有一个队列。而ForkJoinPool每个线程都有自己的队列。
示例-计算1到10000000的和
public static void main(String[] args) { long[] numbers = LongStream.rangeClosed(1, 100000000).toArray(); SumTask ta = new SumTask(numbers,0,numbers.length - 1); ForkJoinPool executorService = (ForkJoinPool) Executors.newWorkStealingPool(5); long l = System.currentTimeMillis(); ForkJoinTask<Long> submit = executorService.submit(ta); System.out.println(submit.join()); System.out.println(System.currentTimeMillis() - l); } //定义任务 private static class SumTask extends RecursiveTask<Long> { private long[] numbers; private int from; private int to; public SumTask(long[] numbers, int from, int to) { this.numbers = numbers; this.from = from; this.to = to; } //此方法为ForkJoin的核心方法:对任务进行拆分 拆分的好坏决定了效率的高低。这里采用的是任务数量大于等于6个就拆分,且是对半拆分 @Override protected Long compute() { // 当需要计算的数字个数小于6时,直接采用for loop方式计算结果 if (to - from < 6) { long total = 0; for (int i = from; i <= to; i++) { total += numbers[i]; } return total; } else { // 否则,把任务一分为二,递归拆分(注意此处有递归)到底拆分成多少分 需要根据具体情况而定 int middle = (from + to) / 2; SumTask taskLeft = new SumTask(numbers, from, middle); SumTask taskRight = new SumTask(numbers, middle + 1, to); taskLeft.fork(); taskRight.fork(); return taskLeft.join() + taskRight.join(); } } }
执行,计算花费时间
newWorkStealingPool(2):1670 (两个线程) newWorkStealingPool(5):1396 (5个线程) newWorkStealingPool(10):1219 (10个线程)
4 线程池的创建
在JDK中rt.jar包下JUC(java.util.concurrent)创建线程池有两种方式:通过ThreadPoolExecutor创建,通过工厂类Executors创建(Executors就是Executor的一个工具类,用来创建线程池、线程工厂、任务) ,其中 Executors又可以创建 6 种不同的线程池类型
1)FixedThreadPool(n)
创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
2)CachedThreadPool()
短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
3)SingleThreadExecutor()
创建一个单线程线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
4)ScheduledThreadPool(n)
创建一个数量固定的线程池,支持执行定时性或周期性任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
5)SingleThreadScheduledExecutor()
此线程池就是单线程的 ScheduledThreadPoo
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
6)WorkStealingPool(n):J创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。
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);
}
线程池执行任务示例
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); FutureTask t1 = new FutureTask(()->{ System.out.println(Thread.currentThread().getName() + "执行任务"); return Thread.currentThread().getName(); }); FutureTask t2 = new FutureTask(()->{ System.out.println(Thread.currentThread().getName() + "执行任务"); return Thread.currentThread().getName(); }); FutureTask t3 = new FutureTask(()->{ System.out.println(Thread.currentThread().getName() + "执行任务"); return Thread.currentThread().getName(); }); FutureTask t4 = new FutureTask(()->{ System.out.println(Thread.currentThread().getName() + "执行任务"); return Thread.currentThread().getName(); }); FutureTask t5 = new FutureTask(()->{ System.out.println(Thread.currentThread().getName() + "执行任务"); return Thread.currentThread().getName(); }); executorService.submit(t1); executorService.submit(t2); executorService.submit(t3); executorService.submit(t4); executorService.submit(t5); }
pool-1-thread-1执行任务 pool-1-thread-2执行任务 pool-1-thread-2执行任务 pool-1-thread-1执行任务 pool-1-thread-2执行任务
5 自定义线程池
5.1 步骤
1)编写任务类(MyTask),实现Runnable接口;
2)编写线程类(MyWorker),用于执行任务,需要持有所有任务;
3)编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;
4)编写测试类(MyTest),创建线程池对象,提交多个任务测试;
6 常见问题
1) ThreadPoolExecutor 有哪些常用的方法?
submit()/execute():执行线程池
shutdown()/shutdownNow():终止线程池
isShutdown():判断线程是否终止
getActiveCount():正在运行的线程数
getCorePoolSize():获取核心线程数
getMaximumPoolSize():获取最大线程数
getQueue():获取线程池中的任务队列
allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程
2)submit和 execute的区别
submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit() 可以使用 Future 接收线程池执行的返回值。
3)shutdownNow() 和 shutdown() 两个方法有什么区别?
shutdownNow() 和 shutdown() 都是用来终止线程池的
使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;
shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。
4) 线程池中核心线程数量大小怎么设置?
CPU密集型任务:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
IO密集型任务: 比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
另外:线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程;
以上只是理论值,实际项目中建议在本地或者测试环境进行多次调优,找到相对理想的值大小。
5)线程池为什么需要使用阻塞队列
因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。创建线程池的消耗较高。
6)线程池为什么要使用阻塞队列而不使用非阻塞队列
阻塞队列可以保证任务队列中没有任务时,阻塞获取任务的线程,线程进入wait状态,释放cpu资源。
当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。 使得在线程不至于一直占用cpu资源。
线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下 while (task != null || (task = getTask()) != null) {}
7)了解线程池状态吗
通过获取线程池状态,可以判断线程池是否是运行状态、可否添加新的任务以及优雅地关闭线程池等。
RUNNING: 线程池的初始化状态,可以添加待执行的任务。
SHUTDOWN:线程池处于待关闭状态,不接收新任务仅处理已经接收的任务。
STOP:线程池立即关闭,不接收新的任务,放弃缓存队列中的任务并且中断正在处理的任务。
TIDYING:线程池自主整理状态,调用 terminated() 方法进行线程池整理。
TERMINATED:线程池终止状态。
8)知道线程池中线程复用原理吗?
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装
不是每次执行任务都会调用 Thread.start()来创建新线程,而是让每个线程去执行一个“循环任务”
在这个“循环任务”中不停的检查是否有任务需要执行,如果有则直接执行,也就是调用任务中的 run 方法
将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来。
9) 线程池参数设置
(1)corePoolSize
核心线程数 这个应该是最重要的参数,核心线程会一直存活,及时没有任务需要执行。 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
如何设置好需要根据项目业务是CPU密集型和IO密集型的区别
CPU密集型 CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading 很高。 在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序。 CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
IO密集型 IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。 I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。
先看下机器的CPU核数,然后在设定具体参数: 自己测一下自己机器的核数 System.out.println(Runtime.getRuntime().availableProcessors()); 即CPU核数 = Runtime.getRuntime().availableProcessors()
分析下线程池处理的程序是CPU密集型还是IO密集型 CPU密集型:corePoolSize = CPU核数 + 1 IO密集型:corePoolSize = CPU核数 * 2
(2)maximumPoolSize
最大线程数 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。
(3)keepAliveTime
线程空闲时间 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize。 如果allowCoreThreadTimeout=true,则会直到线程数量=0。
(4)queueCapacity
任务队列(阻塞队列) 当核心线程数达到最大时,新任务会放在队列中排队等待执行
无界队列; 队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。 阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而博主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。 当然这种队列,maximumPoolSize 的值也就无效了。 当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。 这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
有界队列; 当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。 常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。 PriorityBlockingQueue中的优先级由任务的Comparator决定。 使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
同步移交队列;如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。 SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。 只有在使用无界线程池或者有饱和策略时才建议使用该队列。
(5)allowCoreThreadTimeout
允许核心线程超时
(6)rejectedExecutionHandler
任务拒绝处理器
10)线程池默认值
corePoolSize = 1
maxPoolSize = Integer.MAX_VALUE
queueCapacity = Integer.MAX_VALUE
keepAliveTime = 60s
allowCoreThreadTimeout = false
rejectedExecutionHandler = AbortPolicy()