Executor任务执行框架

前言

官方文档中Executor是一个将任务Runnable的提交与执行进行分离的对象。Executor接口中唯一入口是execute方法,类似于Runnable的run方法。在实际的应用系统中,Runnable作为Thread的传入参数(或者运行对象),由Thread的start方法进行启动。程序在线程中进行调用的入口是Runnable的run,那么为什么又需要一个Executor来执行Runnable?通过new Threa(Runnable).start()不就可以实现任务的线程调用吗?Runnable提交与分离分别又是指代什么内容呢?Executor又能用于什么场景的编程环境?这些问题都是本篇文章的写作目的。

JVM内存模型及线程
在当代计算系统中,程序执行的最小单位是线程Thread。进程Process是应用程序的单位,在进程中同时开启多个线程Thread用于相互独立的任务Task(抽象、离散的工作单位)的并行计算,从而提高物理CPU的利用率,程序获得良好的吞吐量和快速的响应性。如果在应用程序中对于每个任务Task都开启一个线程Thread来执行,那么是否这个应用序就能够高效运行呢?线程在创建的过程中是需要消耗系统资源,也就是CPU、内存。当前计算系统的CPU的计算频率有大小限制,当启动的线程太多,容易造成CPU的超负荷工作,从而导致系统宕机或者拒绝执行风险。抛开操作系统中进程、线程以及调度问题。在JVM的内存模型,每个线程都具有自己的虚拟机栈VM Stack、本地方法栈native Stack和程序计数器,每创建一个线程,都需要耗费资源为这些栈内存开辟空间,同时还有Thread对象、Runnable对象在堆中的内存以及系统CPU创建的内核线程的调用、CPU线程调度等,如下图。那么,线程并行计算所带来的性能提升和线程创建、协同与调度带来的性能消耗是并行程序性能评价的关键因素之一。

JVM内存模型


线程池
在程序开发过程中,应用程序在一定范围内增加线程可以提高系统的吞吐量,但是一旦超出了这个范围,再创建更多的线程则会拖垮程序。例如,WebServer服务器响应用户请求,在并发量比较小的情况下,服务器完全可以响应用户的请求,但是当并发量达到一定的数量后,服务器响应出现响应缓慢、异常或宕机等问题。一个用户请求结束后,处理这个请求的线程则等待JVM的垃圾收集器GC进行回收,当下一次用户重新请求时,又创建一个全新的线程进行处理,如此往复。在服务端的内存中则贮存了许多使用过的线程对象,而JVM的GC回收内存的时间又不确定,则容易造成server端内存消耗。考虑这样一种思路,限制服务端线程的创建个数,同时将这些创建线程进行再利用,也就是处理完一个用户请求后进行回收利用,用于下一个用户请求。例如,公交车运行一样,限制载客数量,当有乘客上车时到达目的地,乘客下车,同时如果有另一个乘客上车,那么又可以乘坐。在SSH框架开发Web应用程序时,在Hibernate配置文档中往往需要配置缓存,在Spring中配置DataSource都是相同的原因。

Executor框架

Executor的核心是将任务Task的提交与实现分离,这句话是什么意思呢?在下面的程序中说明了提交和执行分离的思想。Executor只是一个框架的顶端接口,在抽象层代表的是一个执行器。那么对于执行器而言,它需要具备哪些功能、属性和状态,所以出现了它的实现类、子接口等内容,如下图。
这里写图片描述

public class ExecutorTest {
    public static void main(String[] args) {
        Executor executor = new Executor() {
            public void execute(Runnable command) {
                //在execute进行具体执行,例如同步执行,还是异步执行
                command.run(); //同步执行
                new Thread(command).start(); //异步执行
            }
        };
        //通过execute进行了提交
        executor.execute(new Runnable() {
            public void run() {
                for(int i = 1 ; i <= 100; i++) {
                    System.out.println("Thread" + Thread.currentThread().getId() + " : " + i);
                }
            }
        });
    }
}

ExecutorService框架生命周期管理

Executor框架在进行任务Task的处理过程中涉及几个执行器的状态:运行、关闭、终止。运行状态是Executor执行任务状态,对于不同实现方式的Executor内部执行可能不同。关闭状态是Executor停止对任务的执行,这种停止可以是平缓,也可以是粗暴的方式,为此ExecutorService定义了两种方法:shutdown(启动一次顺序关闭执行以前提交的任务,但不接受新任务),shutdownNow(试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表)。终止状态是请求关闭后所有任务都完成后的状态。常用网络请求来作为例子进行讲解,如下代码所示。ExecutorService的所有API如下图所示。
这里写图片描述

import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class ExecutorServiceTest {
    public static void main(String[] args) {
        try {
            new Thread(new NetworkService(10010, 10)).start();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

class NetworkService implements Runnable  {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    public NetworkService(int port, int poolSize) throws IOException{
        serverSocket = new ServerSocket(port);
        pool = Executors.newFixedThreadPool(poolSize);
    }

    public void run() {
        try {
            while(true) {
                // 监听服务器是否有用户请求
                pool.execute(new Handler(serverSocket.accept()));
            }
        } catch(IOException ex) {
            ex.printStackTrace();
            pool.shutdown(); //当出现因为文件读写而抛出的异常时,关闭Executor;
            //此时Executor的线程池中的任务是拒接接受新的任务提交,正在执行的继续执行,等待执行的等待执行完成
            //直到所有的任务都完成了,则Executor终止
        }
    }
}

class Handler implements Runnable {
    private final Socket socket;
    Handler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            Reader reader = new InputStreamReader(socket.getInputStream());
            int ch = reader.read();
            while(ch >= 0) {
                System.out.print((char)ch);
                ch = reader.read();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
AbstractExecutorService默认实现

ExecutorService是一个接口,它和Executor本身都带着API,由AbstractExecutorService提供部分API的默认实现方式,其中核心的API为submit、invokeAny和invokeAll。下图给出了它的实现关系。
这里写图片描述

  • submit方法__提交任务
 /**
  * 通过提交Runnable到Executor中用于执行,同时返回该Runnable所对应的Future
  * 该Future是Runnbale在执行过程中的状态对象,而不是Executor的状态对象
  */
 public Future<?> submit(Runnable task) {
     //不能将null提交,进行空检查
     if (task == null) throw new NullPointerException();
     FutureTask<Object> ftask = new FutureTask<Object>(task, null);
     execute(ftask);
     return ftask;
 }

/**
 * 通过提交Runnable到Executor中用于执行,同时返回该Runnable所对应的Future
 * 该Future是Runnbale在执行过程中的状态对象,而不是Executor的状态对象
 * 当Runnable成功完成时则返回result
 */ 
 public <T> Future<T> submit(Runnable task, T result) {
    //不能将null提交,进行空检查
    if (task == null) throw new NullPointerException();
    FutureTask<T> ftask = new FutureTask<T>(task, result);
    execute(ftask);
    return ftask;
 }

 /**
  * 通过提交Callable到Executor中用于执行,同时返回该Callable所对应的Future
  * 该Future是Callable在执行过程中的状态对象,而不是Executor的状态对象
  */
 public <T> Future<T> submit(Callable<T> task) {
    //不能将null提交,进行空检查
    if (task == null) throw new NullPointerException();
    FutureTask<T> ftask = new FutureTask<T>(task);
    execute(ftask);
    return ftask;
 }
  • invokeAny 执行给定的任务
/**
 * 如果某个任务已成功完成,则返回其结果,退出Executor
 */
public <T> T invokeAny(Collection<Callable<T>> tasks) throws                                                
    InterruptedException, ExecutionException {
        //内部调用doInvokeAny方法来实现
        ... ... 
    }

/**
 * 如果在给定的超时期满前某个任务已成功完成,则返回其结果
 */
public <T> T invokeAny(Collection<Callable<T>> tasks, long timeout,     
    TimeUnit unit) throws InterruptedException, ExecutionException, 
    TimeoutException {
        //内部调用doInvokeAny方法来实现
        ... ... 
    }
/**
 * the main mechanics of invokeAny.
 */
private <T> T doInvokeAny(Collection<Callable<T>> tasks,
    boolean timed, long nanos) throws InterruptedException,    
        ExecutionException, TimeoutException {
   if (tasks == null) //非null判断
       throw new NullPointerException();
   int ntasks = tasks.size();
   if (ntasks == 0) //集合非空判断
      throw new IllegalArgumentException();
   //每一个Callable对应一个Future
   List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);

   /**
    * this指代当前Executor对象,执行任务
    */
   ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);

    /**
     * 为了效率考虑,尤其是并行限制情况下,对提交的任务进行检查,是否已经提交过。因
     * 为重复提交相同的任务和异常机制会导致mainloop的混乱。
     */

    try {
       // ee用于保存异常,如果未能够获取任何结果则抛出最后获得的异常
       ExecutionException ee = null;
       long lastTime = (timed)? System.nanoTime() : 0;
       Iterator<Callable<T>> it = tasks.iterator();

        // 取任务集合中的第一个任务 
        futures.add(ecs.submit(it.next()));
        --ntasks; //将当前任务个数减一,记录已经有多少个任务被提交了
        int active = 1; //已经提交了第一个任务,active用于记录提交了几个任务并且在执行

        /**
         * 轮询当前Executor是否已经有任务完成了,一旦完成则通过Future.get获取结
         * 果,如果在get过程中抛出异常,那么继续轮询Executor。此外,当Executor中
         * 的线程队列中已经没有任务可以执行,同时tasks中还有任务待执行,则提交任务
         * 到Executor中进行执行,并且进行轮询Executor获取结果。
         */
        for (;;) {
           Future<T> f = ecs.poll(); //获取已经提交的任务的执行结果
           if (f == null) { //如果为空,分成四种情况进行处理
               if (ntasks > 0) { // 首先,从任务集合tasks中取出一个任务提交Executor执行
                   --ntasks;
                   futures.add(ecs.submit(it.next()));
                   ++active;
                } else if (active == 0)//其次,任务集合task已经没有任务了,此时也没有任务在Executor中执行了,那么退出轮询
                  break;
                else if (timed) {//其次,如果设置了时间限制,那么进行时间比较
                    //Executor等待预设的时间nanos来获取任务执行结果Future
                    f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                    if (f == null)//等待时间过去仍然没有获取到Future,则抛出超时异常
                       throw new TimeoutException();
                    /**
                     * 这个步骤是取系统时间来更新等待时间,也就是逐渐缩短等待时间
                     */
                    long now = System.nanoTime();
                    nanos -= now - lastTime;
                    lastTime = now;
                } else //最后,如果任务集合没有任务,当前Executor还有任务没执行完成,而timed没有进行等待时间设置,那么进行等待执行结果
                  f = ecs.take();
               }
             if (f != null) { //如果获取到了任务的执行结果Future
                --active; //已经有一个任务执行完成,则减一
                try {
                    return f.get(); //返回当前任务的Future的结果
                /**
                 * 查看当前任务是否是因为异常而完成,如果是则记录当前异常,继续轮询获取结果
                 */    
                } catch(InterruptedException ie) {
                   throw ie;
                } catch(ExecutionException eex) {
                   ee = eex;
                } catch(RuntimeException rex) {
                   ee = new ExecutionException(rex);
                }
             }
         }    
         //如果最后仍然没有获取到结果,而ee为null,则抛出执行异常
         if (ee == null)
            ee = new ExecutionException();
        throw ee;
     } finally {
        //关闭任务,释放资源
        for (Future<T> f : futures) 
            f.cancel(true);
     }
}
  • invokeAll 执行给定的任务集合
/**
 * 当所有任务完成时,返回保持任务状态和结果的 Future 列表,退出Executor
 */
public <T> List<Future<T>> invokeAll(Collection<Callable<T>> tasks, 
    long timeout, TimeUnit unit) throws InterruptedException {
    //判断非null,抛出空指针异常
    if (tasks == null || unit == null)
       throw new NullPointerException();
    long nanos = unit.toNanos(timeout);
    //预设一个task相关的结果Future集合
    List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    boolean done = false; //完成标识
    try {
       //将任务task集合和结果Futrue集合关联到FutureTask,以便以后Executor提交执行
       for (Callable<T> t : tasks) 
           futures.add(new FutureTask<T>(t));

       long lastTime = System.nanoTime();

       //防止Executor没有足够的并行资源,提交检查、调用的交错检查
       Iterator<Future<T>> it = futures.iterator();
       //首先将所有的任务提交给Executor执行
       while (it.hasNext()) {
           execute((Runnable)(it.next())); //因为execute需要的是Runnable
           long now = System.nanoTime();
           nanos -= now - lastTime;
           lastTime = now;
           if (nanos <= 0) //如果超时,则返回
              return futures; 
       }
       //轮询当前futures集合,确定是否已经全部完成
       for (Future<T> f : futures) {
          if (!f.isDone()) { //如果还未完成
             if (nanos <= 0)  //如果已经超时
                return futures; 
             try { //未到规定时间,则等待nanos时间进行获取
                f.get(nanos, TimeUnit.NANOSECONDS); 
             } catch(CancellationException ignore) {
             } catch(ExecutionException ignore) {
             } catch(TimeoutException toe) {
                return futures;
             }
             //缩短等待时间
             long now = System.nanoTime();
             nanos -= now - lastTime;
             lastTime = now;
        }
    }
    done = true;
    return futures;
  } finally {
    if (!done) //如果是异常、超时等情况引起的方法结束,那么将所以Future进行取消,从而释放资源
       for (Future<T> f : futures) 
           f.cancel(true);
  }
}

invokeAny和invokeAll都是对提交的Collection任务集合进行提交执行。在它们的源码中可以看到,invokeAny是采用了及时获取原则;invokeAll则是一次提交,全部获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值