ExecutorService,它使用线程池中一个或者可能多个线程执行每个提交的任务,通常使用{@link Executors}工厂方法配置。
线程池解决了两个不同的问题:它们通常在执行大量异步任务时提供改进的性能,这是由于减少了每个任务的调用开销,并且它们提供了一种绑定和管理资源的方法,包括执行任务集合时所消耗的线程。每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成任务的数量。
为了在各种上下文中有用,该类提供了许多可调参数和可扩展性钩子。 但是,程序员应该使用更方便的Executors工厂方法newCachedThreadPool(无界线程池,自动线程回收),newFixedThreadPool(固定大小的线程池)和newSingleThreadExecutor(单个后台线程),预配置最常见使用方案的设置。否则,在手动配置和调整此类时,请使用以下指南:
Core and maximum pool sizes,核心和最大池大小
ThreadPoolExecutor将根据corePoolSize和maximumPoolSize设置的边界自动调整池大小
当在方法{@link #execute(Runnable)}中提交新任务并且运行的线程少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。 如果有多个corePoolSize但运行的maximumPoolSize线程少于maximumPoolSize,则只有在队列已满时才会创建新线程。 通过设置corePoolSize和maximumPoolSize相同,您可以创建固定大小的线程池。 通过将maximumPoolSize设置为基本无限制的值(例如{@code Integer.MAX_VALUE}),您可以允许池容纳任意数量的并发任务。 最典型的情况是,核心和最大池大小仅在构造时设置,但也可以使用{@link #setCorePoolSize}和{@link #setMaximumPoolSize}动态更改。
On-demand construction,按需构造
默认情况下,即使核心线程最初只在新任务到达时创建并启动,但可以使用方法{@link #prestartCoreThread}或{@link #prestartAllCoreThreads}动态覆盖。 如果使用非空队列构造池,则可能需要预启动线程。
Creating new threads,创建新线程
使用{@link ThreadFactory}创建新线程。 如果没有另外指定,则使用{@link Executors#defaultThreadFactory},它创建的线程都在同一个{@link ThreadGroup}中,并且具有相同的{@code NORM_PRIORITY}优先级和非守护进程状态。 通过提供不同的ThreadFactory,您可以更改线程的名称,线程组,优先级,守护程序状态等。如果{@code ThreadFactory}在通过从{@code newThread}返回null而无法创建线程时,执行程序将 继续,但可能无法执行任何任务。 线程应该拥有“modifyThread”{@code RuntimePermission}。 如果使用池的工作线程或其他线程不具有此权限,则服务可能会降级:配置更改可能不会及时生效,并且关闭池可能保持可以终止但未完成的状态。
Keep-alive times
如果线程池当前具有超过corePoolSize数量的线程,则多余的线程如果空闲时间超过keepAliveTime,则将终止(请参阅{@link #getKeepAliveTime(TimeUnit)})。 这提供了一种在不主动使用池时减少资源消耗的方法。 如果池稍后变得更活跃,则将构造新线程。 也可以使用方法{@link #setKeepAliveTime(long,TimeUnit)}动态更改此参数。 使用值{@code Long.MAX_VALUE} {@link TimeUnit#NANOSECONDS}可以有效地禁止空闲线程在关闭之前终止。 默认情况下,keep-alive策略仅在有数量超过corePoolSize大小时才适用,但方法{@link #allowCoreThreadTimeOut(boolean)}也可用于将此超时策略应用于核心线程,只要keepAliveTime值不为零即可
Queuing,排队
任何{@link BlockingQueue}都可用于转移和保留提交的任务。此队列的使用与池大小调整交互:
排队有三种常规策略:
拒绝任务
当Executor关闭时,以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时,方法{@link #execute(Runnable)}中提交的新任务将被拒绝。在任何一种情况下,{@code execute}方法都会调用其{@link RejectedExecutionHandler}的{@link RejectedExecutionHandler#rejectedExecution(Runnable,ThreadPoolExecutor)}方法。提供了四种预定义的处理程序策略:
可以定义和使用其他类型的{@link RejectedExecutionHandler}类。 这样做需要一些小心,特别是当策略设计为仅在特定容量或排队策略下工作时
Hook methods
此类提供被调用的{@code protected} overridable {@link #beforeExecute(Thread,Runnable)}和{@link #afterExecute(Runnable,Throwable)}方法执行每项任务之前和之后。 这些可以用来操纵执行环境; 例如,重新初始化ThreadLocals,收集统计信息或添加日志条目。 此外,可以重写方法{@link #terminated}以执行Executor完全终止后需要执行的任何特殊处理。
如果hook,callback或BlockingQueue方法抛出异常,内部工作线程可能会失败,突然终止,并可能被替换。
Queue maintenance,队列维护
方法{@link #getQueue()}允许访问工作队列以进行监视和调试。 强烈建议不要将此方法用于任何其他目的。 当大量排队的任务被取消时,两个提供的方法{@link #remove(Runnable)}和{@link #purge}可用于协助存储回收。
Finalization
程序中不再引用且没有剩余线程的池将自动{@code shutdown}。 如果您希望确保即使用户忘记调用{@link #shutdown}也会回收未引用的池,那么您必须通过设置适当的保持活动时间,使用零核心线程的下限来安排未使用的线程最终死亡 或者设置{@link #allowCoreThreadTimeOut(boolean)}。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl是主要的线程池控制状态,把两个变量包装成一个AtomicInteger
workerCount
,指示线程池中线程的有效数量
runState
,指示线程池是否运行、关闭等
workerCount代表允许启动但是不允许停止的workers的数量,这个值可能短暂不同于存活的线程的实际数量,例如:当一个ThreadFactory在被调用时无法创建一个线程,当退出线程在终止之前仍在执行bookkeeping时。用户可见的线程池大小将记录为worker集合的当前大小。
runState提供主要的生命周期控制,有以下值:
RUNNING:接受新任务并处理排队任务
SHUTDOWN:不接受新任务,但处理排队任务
STOP:不接受新任务,不处理排队任务,并中断正在进行的任务
TIDYING:所有任务都已终止,workerCount为零,转换到状态TIDYING的线程将运行terminate()钩子方法
TERMINATED:方法terminate()已完成
这些值之间的数字顺序很重要,以允许有序比较。 runState随着时间的推移单调增加,但不需要命中每个状态。过渡是:
RUNNING -> SHUTDOWN:在调用shutdown()时,可能隐含在finalize()中
(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()
SHUTDOWN -> TIDYING:当队列和池都为空时
STOP -> TIDYING:当线程池为空
TIDYING -> TERMINATED:当terminate()钩子方法完成后
当状态达到TERMINATED时,在awaitTermination()中等待的线程将返回。
检测从SHUTDOWN到TIDYING的转换不如你想要的那么简单,因为在SHUTDOWN状态期间队列可能在非空后变为空,反之亦然,但我们只能在看到队列为空后又看到workerCount为0(有时需要重新检查)时终止。
方法介绍:
从队列中反复获取任务并执行它们,同时解决了很多问题:
异常机制的净效果是afterExecute和线程的UncaughtExceptionHandler具有我们可以提供的关于用户代码遇到的任何问题的准确信息。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
根据当前配置设置执行blocking或timed wait for a task,如果此worker必须因以下任何一项而退出,则返回null:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
配置线程池的正确姿势:
首先分析任务特性:
任务性质不同的任务可以用不同规模的线程池分开处理。
CPU密集型任务配置尽可能少的线程数量,稍微大于CPU数量即可。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。
我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。
建议使用有界队列,有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千。有一次我们组使用的后台任务线程池的队列和线程池全满了,不断的抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞住,任务积压在线程池里。如果当时我们设置成无界队列,线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。当然我们的系统所有的任务是用的单独的服务器部署的,而我们使用不同规模的线程池跑不同类型的任务,但是出现这样问题时也会影响到其他任务。
线程池的监控
通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用
taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。