Executor框架

一、线程池基本架构顶层是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

           https://blog.csdn.net/qq_24982291/article/details/79163378

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值