问题:
1.单机上一个线程正在处理服务,如果忽然断电了怎么办(正在处理和阻塞队列里的请求怎么处理)
2.为什么要使用线程池,线程池用什么用
3.说说几种常见的线程池及使用场景
4.线程池有哪几种工作队列
5.怎么理解无界队列和有界队列
6.线程池中的几种重要的参数及流程
线程池的概念大家应该都很清楚,帮我们重复管理线程,避免创建大量的线程增加开销。
除了降低开销以外,线程池也可以提高响应速度,了解点 JVM 的同学可能知道,一个对象的创建大概需要经过以下几步:
创建一个对象的开销需要经过这么多步,也是需要时间的嘛,那可以复用已经创建好的线程的线程池,自然也在提高响应速度上做了贡献。
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池
用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
线程池的组成
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的 收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
private final BlockingQueue workQueue; //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小
//、runState等)的改变都要使用这个锁
private final HashSet workers = new HashSet(); //用来存放工作集
private volatile long keepAliveTime; //线程存活时间
private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize; //线程池最大能容忍的线程数
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
submit
方法:
submit
方法会提交一个任务去给线程池执行,该任务可以是带返回结果的 Callable
任务,也可以是一开始就指定结果的 Runnable
任务,或者不带结果的 Runnable
任务(此时即一开始指定结果为 null
)。submit
方法会返回一个与所提交任务相关联的 Future
。通过 上一篇文章 我们可以知道,Future
的 get
方法可以等待任务执行完毕并返回结果。所以通过 Future
,我们可以与已经提交到线程池的任务进行交互。submit
提交任务及任务运行过程大致如下:
Runnable
或者 Callable
任务;newTaskFor
方法构造出 FutureTask
;通过 Future
的 get
方法,获得任务的结果。invokeAll
方法:
invokeAll
方法可以一次执行多个任务,但它并不同等于多次调用 submit
方法。submit
方法是非阻塞的,每次调用 submit
方法提交任务到线程池之后,会立即返回与任务相关联的 Future
,然后当前线程继续向后执行;
而 invokeAll
方法是阻塞的,只有当提交的多个任务都执行完毕之后,invokeAll
方法才会返回,执行结果会以List
返回,该 List
中的每个 Future
是和提交任务时的 Collection
中的任务 Callable
一 一对应的。带 timeout 参数的 invokeAll
就是设置一个超时时间,如果超过这个时间 invokeAll
中提交的所有任务还有没全部执行完,那么没有执行完的任务会被取消(中断),之后同样以一个 List
返回执行的结果。
invokeAny
方法:
invokeAny
方法也是阻塞的,与 invokeAll
方法的不同之处在于,当所提交的一组任务中的任何一个任务完成之后,invokeAny
方法便会返回(返回的结果便是那个已经完成的任务的返回值),而其他任务会被取消(中断)。
举一个 invokeAny
使用的例子:电脑有 C、D、E、F 四个盘,我们需要找一个文件,但是我们不知道这个文件位于哪个盘中,我们便可以使用 invokeAny
,并提交四个任务(对应于四个线程)分别查找 C、D、E、F 四个盘,如果哪个线程找到了这个文件,那么此时 invokeAny
便停止阻塞并返回结果,同时取消其他任务。
shutdown
方法:
shutdown
方法的作用是向线程池发送关闭的指令。一旦在线程池上调用 shutdown
方法之后,线程池便不能再接受新的任务;如果此时还向线程池提交任务,那么将会抛出 RejectedExecutionException
异常。之后线程池不会立刻关闭,直到之前已经提交到线程池中的所有任务(包括正在运行的任务和在队列中等待的任务)都已经处理完成,才会关闭。
shutdownNow
方法:
与 shutdown
不同,shutdownNow
会立即关闭线程池 —— 当前在线程池中运行的任务会全部被取消,然后返回线程池中所有正在等待的任务。
(值得注意的是,我们 必须显式的关闭线程池,否则线程池不会自己关闭)
awaitTermination
方法:
awaitTermination
可以用来判断线程池是否已经关闭。调用 awaitTermination
之后,在 timeout 时间内,如果线程池没有关闭,则阻塞当前线程,否则返回 true
;当超过 timeout 的时间后,若线程池已经关闭则返回 true
,否则返回 false
。该方法一般这样使用:
shutdown
方法向线程池发送关闭的指令;awaitTermination
来检测到线程池是否已经关闭,可以得知线程池中所有的任务是否已经执行完毕;awaitTermination
方法的线程停止阻塞,并返回 true
;isShutdown()
方法,如果线程池已经调用 shutdown
或者 shutdownNow
,则返回 true
,否则返回 false
;
isTerminated()
方法,如果线程池已经调用 shutdown
并且线程池中所有的任务已经执行完毕,或者线程池调用了 shutdownNow
,则返回 true
,否则返回 false
。
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState; static final int RUNNING = 0; static final int SHUTDOWN = 1; static final int STOP = 2; static final int TERMINATED = 3;
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
线程池的处理流程
创建线程池需要使用 ThreadPoolExecutor
类,它的构造函数参数如下:
public ThreadPoolExecutor(int corePoolSize, //核心线程的数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //超出核心线程数量以外的线程空余存活时间
TimeUnit unit, //存活时间的单位
BlockingQueue workQueue, //保存待执行任务的队列
ThreadFactory threadFactory, //创建新线程使用的工厂
RejectedExecutionHandler handler // 当任务无法执行时的处理器
) {...}
参数介绍如注释所示,要了解这些参数左右着什么,就需要了解线程池具体的执行方法ThreadPoolExecutor.execute
:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//1.当前池中线程比核心数少,新建一个线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.核心池已满,但任务队列未满,添加到队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //如果这时被关闭了,拒绝任务
reject(command);
else if (workerCountOf(recheck) == 0) //如果之前的线程已被销毁完,新建一个线程
addWorker(null, false);
}
//3.核心池已满,队列已满,试着创建一个新线程
else if (!addWorker(command, false))
reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}
(线程比作员工,线程池比作一个团队,核心池比作团队中核心团队员工数,核心池外的比作外包员工)
结合这张图,这回流程你明白了吗?
由于 1 和 3 新建线程时需要获取全局锁,这将严重影响性能。因此 ThreadPoolExecutor
这样的处理流程是为了在执行 execute()
方法时尽量少地执行 1 和 3,多执行 2。
在
ThreadPoolExecutor
完成预热后(当前线程数不少于核心线程数),几乎所有的execute()
都是在执行步骤 2。
前面提到的 ThreadPoolExecutor
构造函数的参数,分别影响以下内容:
corePoolSize
:核心线程池数量 maximumPoolSize
:最大线程数量 keepAliveTime
:核心池以外的线程存活时间,即没有任务的外包的存活时间 allowCoreThreadTimeOut(true)
,则核心线程在空闲时头上也会响起死亡的倒计时workQueue
:保存待执行任务的阻塞队列 threadFactory
:每个线程创建的地方 handler
:饱和策略,大家都很忙,咋办呢,有四种策略 CallerRunsPolicy
:只要线程池没关闭,就直接用调用者所在线程来运行任务AbortPolicy
:直接抛出 RejectedExecutionException
异常DiscardPolicy
:悄悄把任务放生,不做了DiscardOldestPolicy
:把队列里待最久的那个任务扔了,然后再调用 execute()
试试看能行不RejectedExecutionHandler
接口自定义策略,比如如记录日志什么的当线程池中的核心线程数已满时,任务就要保存到队列中了。
线程池中使用的队列是 BlockingQueue
接口,常用的实现有如下几种:
了解上面的内容后,我们就可以创建自己的线程池了。
①先定义线程池的几个关键属性的值:
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU 数*2
private static final int MAXIMUM_POOL_SIZE = 64; // 线程池最大线程数
private static final int KEEP_ALIVE_TIME = 1; // 保持存活时间 1秒
②然后根据处理的任务类型选择不同的阻塞队列
如果是要求高吞吐量的,可以使用 SynchronousQueue
队列;如果对执行顺序有要求,可以使用 PriorityBlockingQueue
;如果最大积攒的待做任务有上限,可以使用 LinkedBlockingQueue
。
private final BlockingQueue mWorkQueue = new LinkedBlockingQueue<>(128);
③然后创建自己的 ThreadFactory
在其中为每个线程设置个名称:
private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement());
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
};
④然后就可以创建线程池了
private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY,
new ThreadPoolExecutor.DiscardOldestPolicy());
这里我们选择的饱和策略为 DiscardOldestPolicy
,你可以可以创建自己的。
⑤完整代码:
public class ThreadPoolManager {
private final String TAG = this.getClass().getSimpleName();
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU数*2
private static final int MAXIMUM_POOL_SIZE = 64; // 线程队列最大线程数
private static final int KEEP_ALIVE_TIME = 1; // 保持存活时间 1秒
private final BlockingQueue mWorkQueue = new LinkedBlockingQueue<>(128);
private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement());
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
};
private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY,
new ThreadPoolExecutor.DiscardOldestPolicy());
private static volatile ThreadPoolManager mInstance = new ThreadPoolManager();
public static ThreadPoolManager getInstance() {
return mInstance;
}
public void addTask(Runnable runnable) {
mExecutor.execute(runnable);
}
@Deprecated
public void shutdownNow() {
mExecutor.shutdownNow();
}
}
这样我们就有了自己的线程池。
JDK 为我们内置了五种常见线程池的实现,均可以使用 Executors
工厂类创建。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
不招外包,有固定数量核心成员的正常互联网团队。
可以看到,FixedThreadPool
的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。
此外 keepAliveTime
为 0,也就是多余的空余线程会被立即终止(由于这里没有多余线程,这个参数也没什么意义了)。
而这里选用的阻塞队列是 LinkedBlockingQueue
,使用的是默认容量 Integer.MAX_VALUE
,相当于没有上限。
因此这个线程池执行任务的流程如下:
FixedThreadPool
用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
不招外包,只有一个核心成员的创业团队。
从参数可以看出来,SingleThreadExecutor
相当于特殊的 FixedThreadPool
,它的执行流程如下:
听起来很可怜的样子 - -。
SingleThreadExecutor
用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
全部外包,没活最多待 60 秒的外包团队。
可以看到,CachedThreadPool
没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有 60 秒,超过后就会被回收。
CachedThreadPool
使用的队列是 SynchronousQueue
,这个队列的作用就是传递任务,并不会保存。
因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。
它的执行流程如下:
SynchronousQueue
中提交任务由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool
不会占用任何资源。
CachedThreadPool
用于并发执行大量短期的小任务,或者是负载较轻的服务器。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
定期维护的 2B 业务团队,核心与外包成员都有。
ScheduledThreadPoolExecutor
继承自 ThreadPoolExecutor
, 最多线程数为 Integer.MAX_VALUE
,使用 DelayedWorkQueue
作为任务队列。
ScheduledThreadPoolExecutor
添加任务和执行任务的机制与ThreadPoolExecutor
有所不同。
ScheduledThreadPoolExecutor
添加任务提供了另外两个方法:
scheduleAtFixedRate()
:按某种速率周期执行scheduleWithFixedDelay()
:在某个延迟后执行它俩的代码如下:
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0L)
throw new IllegalArgumentException();
ScheduledFutureTask sft =
new ScheduledFutureTask(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period),
sequencer.getAndIncrement());
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0L)
throw new IllegalArgumentException();
ScheduledFutureTask sft =
new ScheduledFutureTask(command,
null,
triggerTime(initialDelay, unit),
-unit.toNanos(delay),
sequencer.getAndIncrement());
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
可以看到,这两种方法都是创建了一个 ScheduledFutureTask
对象,调用 decorateTask()
方法转成 RunnableScheduledFuture
对象,然后添加到队列中。
看下 ScheduledFutureTask
的主要属性:
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
//添加到队列中的顺序
private final long sequenceNumber;
//何时执行这个任务
private volatile long time;
//执行的间隔周期
private final long period;
//实际被添加到队列中的 task
RunnableScheduledFuture outerTask = this;
//在 delay queue 中的索引,便于取消时快速查找
int heapIndex;
//...
}
DelayQueue
中封装了一个优先级队列,这个队列会对队列中的 ScheduledFutureTask
进行排序,两个任务的执行 time 不同时,time 小的先执行;否则比较添加到队列中的顺序 sequenceNumber ,先提交的先执行。
ScheduledThreadPoolExecutor
的执行流程如下:
具体执行任务的步骤也比较复杂:
DelayQueue.take()
DelayQueue.add()
ScheduledThreadPoolExecutor
用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。
ExecutorService
提供了两种提交任务的方法:
execute()
:提交不需要返回值的任务submit()
:提交需要返回值的任务void execute(Runnable command);
execute()
的参数是一个 Runnable,也没有返回值。因此提交后无法判断该任务是否被线程池执行成功。
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
@Override
public void run() {
//do something
}
});
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future> submit(Runnable task);
submit()
有三种重载,参数可以是 Callable
也可以是 Runnable
。
同时它会返回一个 Funture
对象,通过它我们可以判断任务是否执行成功。
获得执行结果调用 Future.get()
方法,这个方法会阻塞当前线程直到任务完成。
提交一个 Callable
任务时,需要使用 FutureTask
包一层:
FutureTask futureTask = new FutureTask(new Callable() { //创建 Callable 任务
@Override
public String call() throws Exception {
String result = "";
//do something
return result;
}
});
Future> submit = executor.submit(futureTask); //提交到线程池
try {
Object result = submit.get(); //获取结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
线程池即使不执行任务也会占用一些资源,所以在我们要退出任务时最好关闭线程池。
有两个方法关闭线程池:
1.`shutdown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); //获取权限
advanceRunState(SHUTDOWN); //修改运行状态
interruptIdleWorkers(); //遍历停止未开启的线程
onShutdown(); // 目前空实现
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) { //遍历所有线程
Thread t = w.thread;
//多了一个条件w.tryLock(),表示拿到锁后就中断
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
将线程池的状态设置为
SHUTDOWN
,然后中断所有 没有执行 的线程,无法再添加线程。
shutdown
方法的作用是向线程池发送关闭的指令。一旦在线程池上调用 shutdown
方法之后,线程池便不能再接受新的任务;如果此时还向线程池提交任务,那么将会抛出 RejectedExecutionException
异常。之后线程池不会立刻关闭,直到之前已经提交到线程池中的所有任务(包括正在运行的任务和在队列中等待的任务)都已经处理完成,才会关闭。
2.shutdownNow()
public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP); //修改运行状态
interruptWorkers(); //中断所有
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) //中断全部线程,不管是否在执行
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
将线程池设置为 STOP
,然后尝试停止 所有线程,并返回等待执行任务的列表。
它们的不同点是:shutdown()
只结束未执行的任务;shutdownNow()
结束全部。
与 shutdown
不同,shutdownNow
会立即关闭线程池 —— 当前在线程池中运行的任务会全部被取消,然后返回线程池中所有正在等待的任务。
共同点是:都是通过遍历线程池中的线程,逐个调用 Thread.interrup()
来中断线程,所以一些无法响应中断的任务可能永远无法停止(比如 Runnable
)。
了解 JDK 提供的几种线程池实现,在实际开发中如何选择呢?
根据任务类型决定。
前面已经介绍了,这里再小节一下:
CachedThreadPool
用于并发执行大量短期的小任务,或者是负载较轻的服务器。FixedThreadPool
用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。SingleThreadExecutor
用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。ScheduledThreadPoolExecutor
用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。自定义线程池时,如果任务是 CPU 密集型(需要进行大量计算、处理),则应该配置尽量少的线程,比如 CPU 个数 + 1,这样可以避免出现每个线程都需要使用很长时间但是有太多线程争抢资源的情况;
如果任务是 IO密集型(主要时间都在 I/O,CPU 空闲时间比较多),则应该配置多一些线程,比如 CPU 数的两倍,这样可以更高地压榨 CPU。
为了错误避免创建过多线程导致系统奔溃,建议使用有界队列。因为它在无法添加更多任务时会拒绝任务,这样可以提前预警,避免影响整个系统。
执行时间、顺序有要求的话可以选择优先级队列,同时也要保证低优先级的任务有机会被执行。
借鉴大佬的文章,仅供参考。
https://blog.csdn.net/u011240877/article/details/73440993
https://segmentfault.com/a/1190000007925310
https://blog.csdn.net/wolf909867753/article/details/77500625
https://www.cnblogs.com/frinder6/p/5507082.html
http://www.cnblogs.com/absfree/p/5357118.html