1、Java线程池
1-1、线程池的优点
-
减少资源消耗:通过池化技术可以重复利用线程,降低线程创建和销毁的消耗;
-
提高响应速度:任务到达时,可以立刻处理不需要创建线程;
-
提高线程管理:使用线程池可以进行统一分配、调优和监控;无限创建线程会浪费系统资源;
-
丰富功能:可以扩展功能,比如定时线程池ScheduledThreadPoolExecutor,允许任务延迟或定时执行;
1-2、线程池设计
- Executor提供最基本的任务执行功能;
- ExecutorService在任务执行任务执行功能基础上,又定义了【可返回执行结果】的方法(submit),以及对线程生命周期的管理的方法(shutdown、shutdownNow);
-
AbstractExecutorService继承并实现任务执行;
-
ThreadPoolExecutor一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
1-3、线程池的生命周期管理
说明,-1的二进制值为
1-3-1、线程池最多支持的任务数量
CAPACITY = 536870911
二进制:11111111111111111111111111111,29个1;
1-3-2、关于~CAPACITY,用来计算运行状态
~CAPACITY = 11100000000000000000000000000000,29个0;
1-3-3、COUNT_BITS: 值为29,用整数的后29位来管理执行线程的个数;
用前3位管理运行状态;
1-3-4、再看下面这段代码,首先
RUNNING 的值 == **-536870912,**ctl用来记录当前正在运行的线程,就是说,记录线程运行数量的值是负数;
ctl最多为0,如果超过0,线程池就无法在执行新的任务;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
1-3-5、线程池中的状态
运行状态
状态描述
二进制值
十进制值
RUNNING
能接受新提交的任务,并且也能处理阻塞队列中的任务。
-1<<29
111【00000000000000000000000000000】,29个0;
【111】,表示运行状态
-536870912
SHUTDOWN
关闭状态。不在接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
0
【000】,表示关闭状态
0
STOP
停止状态。不接受新的任务,也不处理队列中的任务,并且会中断处理中的任务。
1【00000000000000000000000000000】,29个0,
【001】,表示停止状态
536870912
TIDYING
所有任务都以终止,workerCount(工作线程数量)为0。
10【00000000000000000000000000000】,30个0
【010】,表示TIDYING
1073741824
TERMINATED
terminated()方法执行完成进入此状态
11【00000000000000000000000000000】,29个0
【011】,表示终止状态
1610612736
1-3-6、线程池状态转换
1-4、线程池初始化
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
构造参数说明:
- corePoolSize:核心线程数,或最小线程数,在没有任务执行时线程池的大小,只有在工作队列满了,才会创建超出这个数量的线程;
- maximumPoolSize:最大线程数,表示可同时活动的线程数量上限;
- keepAliveTime: 线程存活时间;与corePoolSize,maximumPoolSize共同负责线程的创建于销毁;如果某个线程的空闲时间超过这个值,将线程标记为可回收的,并且当线程池当前大小超过corePoolSize时,这个线程将被终止;
- unit,空闲线程存活时间单位;
- workQueue,保存待执行任务的队列;
- threadFactory,线程工厂,用于创建新的线程;Executors中提供了默认线程工程DefaultThreadFactory;线程工厂可以【定制化】线程池信息,如线程池的名字(线程名),设置自定义的UncaughtExceptionHandler等;
- handle,拒绝策略,默认的拒绝策略是AbortPolicy;
1、为什么需要固定线程数量?
避免每次收到请求创建线程,减少创建和销毁线程带来的资源消耗,
也能加快请求执行速度;
2、为什么要设置队列?要用阻塞队列?
阻塞队列是线程安全的
任务处理的时间 可能 大于 任务到达的速率,当新任务到达后,没有足够CPU(线程)时,新的任务将无法处理,所以用队列缓存起来;
队列就是缓解任务突增问题;
3、为什么需要拒绝策略(RejectedExecutionHandler)?为什么要捕获RejectedExecutionException?
如果使用有界队列,如果队列放满,线程池的线程数量也达到最大线程数时,新的任务该如何处理成为新的问题;
Java提供的拒绝策略AbortPolicy会抛出RejectedExecutionException异常,因为需要知道哪个任务处理失败或无法处理,用另外的线程唤起重新处理,特别是在生产环境,如果上游通过消息队列请求的数据,或者异步任务提交的数据,如果直接丢失了,会对业务造成影响。
1-5、线程池如何执行任务?
1-6、任务调度机制
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
// 1、小于核心线程,创建
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);
}
else if (!addWorker(command, false))
reject(command);
}
所有任务的调度都是由execute方法完成的,这部分完成的工作是:
检查线程池状态,包括:是否处于运行状态,线程池正在运行线程数和运行策略;
其执行过程如下:
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
- 如果线程池是【运行状态】(线程计数值ctl小于0时),并且任务【入队成功】,继续执行3;
- 进行double check,重新检查运行的线程数量,如果发现线程池无法执行(正在执行的任务 + 待执行任务 超过 CAPACITY),并且任务可以【成功出队】时,根据拒绝策略处理任务;条件不满足,跳转到步骤4;
- 如果工作线程数量(ctl) == 0【代表处理的线程已达到上限】,则创建新的【非核心】线程;
- 如果创建非核心线程失败,根据拒绝策略处理任务;
计算密集型业务,先加队列更好;因为即使创建多余线程,也没有CPU执行;
IO密集型,队列作为缓存,因为IO速度比较慢;
1-7、任务申请
任务的执行有两种可能:
- 一种是任务直接由新创建的线程执行,仅出现在线程初始创建的时候。
- 另一种是线程从任务队列中获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行,线程获取任务绝大多数的情况。
1-8、线程池数量的配置
假如CPU的核数为N,
-
对于【计算密集型】操作,线程池数量为 N 或 N+1,因为线程太多,反倒可能导致大量的上下文切换开销;
-
对于【IO密集型】操作:线程数 = CPU核数 × 目标CPU利用率 ×(1 + 平均等待时间/平均工作时间);
1-9、线程池【核心线程数量】回收
可以,使用setCorePoolSize方法,调节核心线程数量。
public void setCorePoolSize(int corePoolSize) {
// 如果参数小于0,直接抛出异常
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
// 当工作线程大于核心线程,需要中断空闲线程
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) {
// 如果新的核心线程数大于原有核心线程,则扩展线程,假如队列为空,则不增加核心线程数量
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
1-10、线程池的饱和策略
Java提供的饱和策略如下
-
AbortPolicy(中止):是默认的饱和策略,该策略将抛出一个未检查(Unchecked)的RejectedExecutionException,调用者可以捕获这个一次,然后根据需求编写自己的代码。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); }
}
-
DiscardPolicy(抛弃):当新提交的任务无法保存到队列中等待执行时,将会抛弃该任务;
-
DiscardOldestPolicy(抛弃最旧的),抛弃队列中第一个任务(最早入队或者优先级最高的任务);
-
CallerRunsPolicy(调用者运行):实现一个调节机制,该策略既不会抛弃任务,也不会抛出异常,而是讲某些任务回退到调用者,从而降低新任务的流量。
1-11、线程池中的线程是如何创建的
调用以下方法,其中core代表是否为核心线程,true是,false不是;
内部使用ThreadFactory(线程工厂)创建一个线程;
addWorker(Runnable firstTask, boolean core)
1-12、为什么不建议使用 Executors提供的工厂方法创建线程池
1、因为Executors的【newFixedThreadPool】和【newSingleThreadPool】使用的是 LinkedBlockingQueue,这个队列的长度是Integer.MAX_VALUE,如果任务较多并且处理速度慢,队列中任务变得太多,造成内存空间占满;
2、而Executors的【newCachedThreadPool】,是创建一个最大线程数量等于Integer.MAX_VALUE,线程数量会无限增加,导致系统资源占满;
更多线程池说明参考:
2、Executor框架
先看Executor 框架的基本组成
-
Executor 是一个基础的接口,其初衷是将任务提交和任务执行细节解耦,这一点可以体会其定义的唯一方法。
public interface Executor {
/** * Executes the given command at some time in the future. * The command may execute in a new thread, * in a pooled thread, or in the calling thread, * at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command);
}
-
ExecutorService 则更加完善,不仅提供 service 的管理功能,比如 shutdown 等方法,也提供了更加全面的提交任务机制;
public interface ExecutorService extends Executor {
/** * 启动有序关闭,在该关闭中执行先前提交的任务,但不接受任何新任务 */ void shutdown(); /** * 尝试停止所有正在执行的任务,暂停正在等待的任务的处理,并返回正在等待执行的任务的列表。 */ List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); <T> Future<T> submit(Callable<T> task); /** * Submits a Runnable task for execution and returns a Future * representing that task. The Future's {@code get} method will * return the given result upon successful completion. * * @param task the task to submit * @param result the result to return * @param <T> the type of the result * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ <T> Future<T> submit(Runnable task, T result); /** * Submits a Runnable task for execution and returns a Future * representing that task. The Future's {@code get} method will * return {@code null} upon <em>successful</em> completion. * * @param task the task to submit * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ Future<?> submit(Runnable task);
}
-
Executors 则从简化使用的角度,为我们提供了各种方便的静态工厂方法。
Executors 目前提供了 5 种不同的线程池创建配置:
2-1、newCachedThreadPool
newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:
- 初始化核心线程数为0,最大线程数为Integer.MAX_VALUE;
- 会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;
- 如果线程闲置的时间超过 60 秒,则被终止并移出缓存;
- 长时间闲置时,这种线程池,不会消耗什么资源。
- 其内部使用 SynchronousQueue 作为工作队列,SynchronousQueue 每个插入操作必须等待另一个线程进行相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。
极端情况可能导致 OOM;
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2-2、newFixedThreadPool
newFixedThreadPool(int nThreads),重用【固定数目(nThreads)】的线程,
任何时候最多有 nThreads 个工作线程是活动的;
这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;
如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
创建的线程池不会超时;
其内部使用****LinkedBlockingQueue作为工作队列。LinkedBlockingQueue【并不是一个无界队列】,队列最大长度为Integer.MAX_VALUE。
队列中元素无限增加;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2-3、newSingleThreadExecutor
newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
1、单个线程;
2、队列接近 Integer.MAX_VALUE;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2-4、newSingleThreadScheduledExecutor
newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
2-5、newWorkStealingPool
newWorkStealingPool(int parallelism),是java 8 才加入的创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
- 工作队列负责存储用户提交的各个任务,这个工作队列,可以是容量为 0 的 SynchronousQueue(使用 newCachedThreadPool),也可以是像固定大小线程池(newFixedThreadPool)那样使用 LinkedBlockingQueue。
- 内部的“线程池”,这是指保持工作线程的集合,线程池需要在运行过程中管理线程创建、销毁。例如,对于带缓存的线程池,当任务压力较大时,线程池会创建新的工作线程;当业务压力退去,线程池会在闲置一段时间(默认 60 秒)后结束线程。
- ThreadFactory 提供上面所需要的创建线程逻辑。
- 如果任务提交时被拒绝,比如线程池已经处于 SHUTDOWN 状态,需要为其提供处理逻辑,Java 标准库提供了类似ThreadPoolExecutor.AbortPolicy等默认实现,也可以按照实际需求自定义。