一、线程池基本架构顶层是Executor
二、Executor框架的两级调度
在HotSpot VM的线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程。当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU。
在上层,Java多线程程序通常会把应用分解成若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。
其模型图如下: 应用程序通过Executor框架控制上层的调度;下层的调度有操作系统内核控制,下层的调度不受应该程序的控制
1、Executor框架的结构
Executor框架主要又3大部分组成:
(1)任务:包括被执行任务需要实现的接口:Runnable接口或Callable接口。
(2)任务的执行:包括任务执行机制的核心接口Executor,及其子接口ExecutorService接口。 ExecutorService接口的实现类有ThreadPoolExecutor和ScheduledThreadPoolExecutor
(3)异步计算的结果:包括接口Future和其实现类FutureTask类。
2、Executor框架的类与接口关系示意图
(1)Executor接口是Executor框架的基础,它将任务的提交和执行分离开来。
(2)ThreadPoolExecutor是Executor框架最核心的类,用来执行被提交的任务。
(3)ScheduledThreadPoolExecutor是一个实现类,可以定期执行任务。
(4)Future接口和实现Future接口的FutureTash类,代表异步计算的结果。
(5)实现了Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor和ScheduledThreadPoolExecutor执行。
Executor框架的使用示意图:
首先主线程(main线程)创建实现Runnable或者Callable<V> 接口的待执行任务对象。Executors可以将一个Runnable对象封装成一个Callable对象。
然后把Runnable接口对象可以交由ExexutorService执行ExexutorService.execute(....);或者把Runnable接口对象或Callable<V>接口对象交由ExecutorService执行ExecutorService.submit(....) 有返回值,返回值是一个Futrue接口的对象,现阶段JDK返回的是FutureTask,因为FutureTask实现了Future接口,我们可以直接创建FutureTask对象,然后交给ExecutorService执行。
最后,主线程执行 FutrueTask.get()阻塞,等待任务执行完成,同时获取返回值。也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)取消执行(参数表示如果正在执行是否取消)。
三、Executor框架的成员详解
1、成员一:ThreadPoolExecutor :它是Executor框架最核心的类,是线程池的实现类。主要有4个组件:
(1)、corePool:核心线程池的大小
(2)、maximumPool:最大线程池的大小
(3)、BlockingQueue:用来暂时保存任务的工作队列
(4)、RejecteExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和(达到了最大线程池的大小并且工作队列已经满了),execute()方法将调用Handler
通过Executor框架的工厂类Executors 创建,Executors可以创建三种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool。
(1)FixedThreadPool 创建固定线程数的线程池,适用于限制当前线程数量的场景,适用于负载较重的服务器。Executor创建使用的API:
public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
可以看看第一个API,返回一个线程池:
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThread,nThread, 0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。 其中的execute()方法运行如下:
1、如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
2、在线程池完成预热之后(当前线程中有一个运行的线程),将任务加入LinkedBlockingQueue。
3、线程执行完1中的任务后,会在一个无线循环中反复从LinkedBlockingQueue获取任务来执行。
FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响:
a、当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程不会超过corePoolSize。
b、maximumPoolSize变为一个无效参数。
c、keepAliveTime也变为一个无效参数。
d、永远不会执行饱和策略:因为运行中的 FixedThreadPool不会执行shutdown()或者shutdownNow()
(2)SingleThreadExecutor 创建单个线程,适用于需要保证顺序执行各任务;并且在任意时间点,不会有多个线程是活动的场景:
public static ExecutorService newSingleThreadPool();
public static ExecutorService newSingleThreadPool(ThreadFactory threadFactory);
同样看看第一个方法的源码:
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数于FixedThreadPool一样,并使用LinkedBlockingQueue无界队列作为工作队列。 工作示意图:
(3)CachedThreadPool 根据需要创建新线程,大小无界的线程池,适用于执行大量短期的异步任务的小程序,或负载较轻的服务器。
public static ExecutorService newCachedThreadPool();
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
看看第一个方法源码:
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDE,
new SynchronousQueue<Runnable>());
}
CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool时无界的。keepAliveTime被设置为60L,意味着空闲线程超过60秒后被终止。
CachedThread使用没有容量的SynchronousQueue作为线程池的工作队列,但其maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度是,CachedThreadPool会不断创建新的线程,极端情况下,会耗尽CPU和内存资源。 其执行示意图如下:
(1)、首先执行SynchronousQueue.offer(Runnable task)。如果当前有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECODES),则配对成功,将任务交给空闲线程执行。
(2)、当没有空闲线程时,创建一个新线程执行任务。
(3)、线程在执行任务完毕后,执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECODES),向队列请求任务,并等待60秒。如果60之后仍没有新任务,则被终止。如果有新任务则继续执行。
2、成员二:ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承于ThreadPoolExecutor,主要用来给定的延迟之后运行任务或定期执行任务,功能于Timer相识,但比它更强大。通常使用Executors工厂类创建, Executors可以创建两种类型的ScheduldThreadPoolExecutor:
(1)ScheduledThreadPoolExecutor 包含若干个线程,适用于多个线程执行周期任务,同时限制执行的线程数量。
Executors 创建固定个数线程ScheduledThreadPoolExecutor 的API:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);
(2)SingleThreadScheduledExecutor 之包含一个线程,适用于单个后台线程执行定时任务,同时保证顺序执行各个任务。
Executors 创建单个线程SingleScheduledExecutor 的API:
public static ScheduledExecutorService newSingleScheduledExecutor(int corePoolSize);
public static ScheduledExecutorService newSingleScheduledExecutor(int corePoolSize, ThreadFactory threadFactory);
为了实现周期性的执行任务,ScheduledThreadPoolExecutor做了如下的修改:
1、使用DelayQueue作为任务队列。
2、获取任务的方式不同。
3、执行周期任务后,增加了额外的处理。
执行示意图如下:
1)、当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()或scheduleWithFixedDelay()时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。
2)、线程池中的线程从DelayQueue中获取ScheduledFutureTask并执行任务。
ScheduledFutureTask中有三个成员变量:
long型变量time,表示这个任务将要被执行的具体时间。
long型变量sequenceNumbe,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
long型成员变量period,表示任务执行的间隔周期。
DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对对列中的ScheduledFutureTask进行排序。time小的先执行,被排在前面(时间早的任务先被执行)。如果两个任务time相同则比较sequenceNumbe(提交顺序:先提交先执行)。
ScheduledThreadPoolExecutor中的线程执行周期任务的步骤如下:
1、线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time(被执行的具体时间)大于等于当前时间。
2、线程1执行这个ScheduledFutureTask。
3、线程1修改ScheduledFutureTask的time变量为下次要被执行的时间。
4、线程1把修改time之后的ScheduledFutureTask放入DelayQueue(DelayQueue.add())。
3、成员三:FutureTask 详解
很多时候我们希望创建一个新线程去执行一个功能相对独立的任务,并在任务完成之后返回执行结果。如果实现Runnable接口,没有办法获得返回值;如果实现Callable接口,必须使用ExecutorService来执行,不能使用更简单灵活的new thread方法来实现。为了解决上面两个问题,于是有了FutureTask。FutureTask实现了Runnable, Future<V>,所以它既可以通过new thread来跑任务,也可以通过ExecutorService来管理任务,同时FutureTask还提供了超时返回的功能。另外还有一个非常有用的功能是它提供了一个可重载方法done(),done()会在任务执行完之后被调用,用户可以override该方法,来执行一些数据清理,句柄释放等操作。
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable接口或Callable接口的实现类通过submit()方法提交给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,会返回一个FutureTask对象。
注意:截止到JDK8为止,返回是FutureTask对象,但Java API只是保证返回一个实现Future接口的对象,并不一定是FutureTask。
FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给Executor执行,也可以由调用线程直接执行(FutureTask.run())。FutureTask有未启动、已启动、已完成3种状态。
1)、未启动。FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态。当创建一个FutureTask,且没有执行FutureTask.run()方法之前,这个FutureTask处于未启动状态。
2)、已启动。FutureTask.run()方法被执行的过程中,FutureTask处于已启动状态。
3)、已完成。FutureTask.run()方法执行完后正常结束,或被取消(FutureTask.cancel()),或FutureTask.run()时抛出异常而异常结束,FutureTask处于已完成状态。
当FutureTask处于未启动或者已启动状态好时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态,执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。执行示意图如下:
当FutureTask处于未启动,执行FutureTask.cancel()方法导致此任务永远不被执行;
当FutureTask处于已启动,执行FutureTask.cancel(true)方法将以中断执行次任务方式来试图停止任务; 执行FutureTask.cancel(flase)方法不会对任务造成影响。
当FutureTask处于已完成,执行FutureTask.cancel(...)方法返回false;
API接口:
FutureTask的实现原理
FutureTask的实现基于AQS(AbstractQueuedSynchranizer)。java.util.concurrent中的很多阻塞类(比如ReentrantLock)都是基于AQS来实现的。AQS是一个同步框架,它提供了通用机制来原子性管理同步状态、阻塞和唤醒线程、以及维护被阻塞线程的队列。基于了AQS实现的同步器有:ReentrantLock、Semaphore、ReentrantLockReadWriteLock、CountDownLatch和FutureTask。
每一个基于AQS实现的同步器都包含了两种类型的操作:
(1)至少有一个acquire操作:这个操作阻塞调用线程,除非/直到AQS的状态允许这个线程继续执行。FutureTask的acquire操作为get()/get(long timeout,TimeUnit unit)的调用。
(2)至少有一个release操作。这个操作改变AQS的转态,改变后的转态可允许一个或多个阻塞线程被解除阻塞。FutureTask的release操作包括run()和cancel()方法
FutureTask的核心是Sync,Sync是FutureTask的私有内部类,它继承于AQS。Sync实现了AQS的tryAcquiresShared(int)方法和tryReleaseShared(int)方法,通过这两个方法来检查和更新同步状态。FutureTask对外暴露的get()、run()、cancel()都直接托付给内部私有类Sync。
Sync和ReentrankLock中的Sync一样,继承了AQS,通过AQS中的state来表示任务不同的执行状态:
1表示正在运行;2表示已经运行结束;4表示任务取消了。
private final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -7828117401763700385L;
/** State value representing that task is running */
private static final int RUNNING = 1;
/** State value representing that task ran */
private static final int RAN = 2;
/** State value representing that task was cancelled */
private static final int CANCELLED = 4;
/** The underlying callable */
private final Callable<V> callable;
/** The result to return from get() */
private V result;
/** The exception to throw from get() */
private Throwable exception;
FutureTask.get()方法会调用:调用的Sync中的innerGet()方法:就是调用AQS.acquiresSharedInterruptibly(int arg)
public V get() throws InterruptedException, ExecutionException {
return this.sync.innerGet();
}
public V get(long arg0, TimeUnit arg2) throws InterruptedException,
ExecutionException, TimeoutException {
return this.sync.innerGet(arg2.toNanos(arg0));
}
调用的Sync中的innerGet()方法:就是调用AQS.acquiresSharedInterruptibly(int arg)
V innerGet() throws InterruptedException, ExecutionException {
this.acquireSharedInterruptibly(0);
if (this.getState() == 4) {
throw new CancellationException();
} else if (this.exception != null) {
throw new ExecutionException(this.exception);
} else {
return this.result;
}
}
acquireSharedInterruptibly方法是AQS里面非常重要的一个方法,他以轮询的方式来阻塞线程,直到获得中断或者满足退出轮询条件来终结果阻塞,运行后面的代码。acquireSharedInterruptibly会回调Sync的tryAcquireShared方法,当Sync中实现的tryAcquireShared方法返回值<0时,会使调用的线程阻塞。
protected int tryAcquireShared(int arg0) {
return this.innerIsDone() ? 1 : -1;
}
boolean innerIsDone() {
return this.ranOrCancelled(this.getState()) && this.runner == null;
}
private boolean ranOrCancelled(int state) {
return (state & 6) != 0;//state只会=1,2,4.只有当=1(表示正在执行)的时候才=0
}
当当前状态不等于1(执行完或者被取消),且this.runner=null的时候,就表示acquire操作成功。
如果成功则get()方法立即返回,失败则到线程等待队列中等待其它线程执行release操作。当其他线程执行release操作,即FutureTask.run()或FutureTask.cancel()唤醒当前线程后,当前线程再次执行tryAcquireShared()将返回1,当前线程离开线程等待队列并唤醒它的后继线程。最后返回计算结果。
再来看看FutureTask.run()方法:
public void run() {
this.sync.innerRun();
}
Sync中:
void innerRun() {
if (this.compareAndSetState(0, 1)) {
try {
this.runner = Thread.currentThread();//取得当前线程
if (this.getState() == 1) {//再检查一次,双重保障
this.innerSet(this.callable.call());//将call()方法的执行结果赋值给Sync中的result.
} else {//如果不等于1,表示又被取消或者是抛出了异常。这时候唤醒调用get的线程。
this.releaseShared(0);
}
} catch (Throwable arg1) {
this.innerSetException(arg1);//设置异常
}
}
}
如注释,先用CAS来设置当任务的状态还是初始化完后默认值0的时候,将任务的运行状态为运行中,再获取当前线程。获取到后,再判断一次任务的状态是不是1。因为任务可能又被取消了(取消的状态是4)。如果是1,然后运行call方法,并将结果设置到Sync中的result字段,然后调用get()的时候就会返回结果。如果状态不等于1,那就调用releaseShared()方法,然后唤醒等待get()的线程。如果这中间抛出了异常,就设置Sync的Exception字段。这样的话,在我们前面说到的get()方法的时候,就会抛出这个异常,而不会返回结果。
借鉴:https://blog.csdn.net/FoolishAndStupid/article/details/76185217