java多线程(3)---Executors线程池源码分析

一.前言
     Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。经过这样的封装,对于使用者来说,提交任务获取结果的过程大大简化,调用者直接从提交的地方就可以等待获取执行结果。

二.简单例子
ExecutorService pool = Executors.newFixedThreadPool(7);
//提交作业,作业内容封装在Callable中,约定好了输出的类型是String。
Future result = pool.submit(new Callable<String>() {
             public String call() throws Exception
             {
                 return "我是一个要被线程执行的任务!";
             }
         });
String outputs = result.get().toString();
System.out.println(outputs);
三.源码分析

主要步骤:Task提交,Task执行,获取Task执行结果。
大体过程:
     构建线程池时定义了一个额定大小,当线程池内工作线程数小于额定大小,有新任务提交就创建新工作线程,如果超过该阈值,把接收任务加到任务队列里面。
     但是如果任务队列里的任务实在太多了,那还是要申请额外的工作线程来帮忙,如果还是不够用就拒绝服务。
     执行时不断从任务队列中获取任务进行执行。


1.Task任务提交
        从类图上可以看到,接口ExecutorService继承自Executor,抽象类AbstractExecutorService类实现了ExecutorService接口, ThreadPoolExecutor又继承了 AbstractExecutorService类。


(1) 看AbstractExecutorService中代码提交部分:
  public <T> Future<T>submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask =newTaskFor(task);//将Callable类型的参数封装成FutureTask
       execute(ftask);
        return ftask;
  }
  protected <T> RunnableFuture<T>newTaskFor(Callable<T> callable) {
        return newFutureTask<T>(callable);
  }
       构造好一个FutureTask对象后,调用execute()方法执行任务。这个方法是顶级接口Executor中定义的最重要的方法。
       从上面看出,FutureTask既能被执行,又能被返回,是因为:
  •  FutureTask类型实现了Runnable接口,因此满足Executor中execute()方法的约定。
  •  FutureTask类型同时又实现了Future接口,该对象在execute执行后,就又作为submit方法的返回值返回,因为FutureTask实现了Future接口,满足Future接口的约定。

       
        观察下子类 ThreadPoolExecutor ,创建线程池实际是生成该类的一个对象:
先补充下ThreadPoolExecutor的两个最重要的数据结构,分别是存储接收任务的任务队列和用来干活的作业集合
//任务队列(阻塞队列,参看之前关于它的笔记)
private final BlockingQueue<Runnable> workQueue;
//作业线程集合
private final HashSet<Worker> workers = new HashSet<Worker>();
        简单梳理下思路:
        构建线程池时定义了一个额定大小,当线程池内工作线程数小于额定大小,有新任务进来就创建新工作线程,如果超过该阈值,把接收任务加到任务队列里面。但是如果任务队列里的任务实在太多了,那还是要申请额外的工作线程来帮忙。如果还是不够用就拒绝服务。
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {//第一个条件满足,||后面的将不再执行
            if (runState == RUNNING && workQueue.offer(command)) {//线程数超过阈值,不再创建线程,将任务加入阻塞队列
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
            else if (!addIfUnderMaximumPoolSize(command))
                reject(command); // is shutdown or saturated
        }
}
          addIfUnderCorePoolSize方法检查如果当前线程池的大小小于配置的核心线程数,说明还可以创建新线程,则启动新的线程执行这个任务。
   private boolean addIfUnderCorePoolSize(Runnable firstTask) {
        Thread t = null;
       //如果当前线程池的大小小于配置的核心线程数,说明还可以创建新线程,就为该任务创建一个线程去处理
            if (poolSize < corePoolSize && runState == RUNNING)
      // 则启动新的线程执行这个任务
                t = addThread(firstTask);       
        return t != null;
    }
        调用adThread方法创建一个工作线程,差别是创建的有些工作线程上面关联接收到的任务firstTask,有些没有。该方法为当前接收到的任务firstTask创建Worker,并将Worker添加到作业集合HashSet<Worker> workers中,并启动作业。
private ThreadaddThread(Runnable firstTask) { 
        Worker w = new Worker(firstTask);//为当前接收到的任务firstTask创建Worker,即将task和worker绑定
        Thread t = threadFactory.newThread(w);//将该作业worker绑定一个线程
        w.thread = t;
        //将Worker添加到作业集合HashSet<Worker> workers中,并启动作业
        workers.add(w);
        t.start();
        return t;
    }
至此,任务提交过程简单描述完毕。

2.任务执行
       上面作业线程启动start之后,就要执行Worker的run方法,看作业线程干什么当然是看它的run方法在干什么。如我们所料,作业线程就是在一直调用getTask方法获取任务,然后调用 runTask(task)方法执行任务。在while循环里面,就是不干完不罢休的意思!
    public voidrun() {
            try {
                Runnable task = firstTask;
                //循环从线程池的任务队列workQueue获取任务
                while (task != null || (task =getTask()) != null) {
                //执行任务
                    runTask(task);
                    task = null;
                }
            } finally {
                workerDone(this);
            }
        }
     然后简单看下Worker的两个方法getTask和runTask(task)方法的内容。
     getTask方法是ThreadPoolExecutor提供给其内部类Worker的的方法。getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;
Runnable getTask() {
        for (;;) {
                  //从任务队列的头部取任务
                  r = workQueue.take();
                    return r;
          }
    }
      runTask(Runnable task)是工作线程Worker真正处理拿到的每个具体任务。
private void runTask(Runnable task) {         
                  //调用任务的run方法,即在Worker线程中执行Task内定义内容。
                    task.run();
        }
     需要注意的地方出现了,调用的其实是 FutureTask   task的run方法。FutureTask在线程池中等待被执行,最终执行的是FutureTask的run方法。看下FutureTask的run方法做了什么事情。
     FutureTask即有Runnable接口的run方法来定义任务内容,也有Future接口中定义的get、cancel等方法来控制任务执行和获取执行结果。
     Runnable接口自不用说,Future接口的伟大设计,就是使得实现该接口的对象可以阻塞线程直到任务执行完毕,也可以取消任务执行,检测任务是执行完毕还是被取消了。想想在之前我们使用Thread.join()或者Thread.join(long millis)等待任务结束是多么苦涩啊。
     和其他的同步工具类一样,FutureTask的主要工作内容也是委托给其定义的内部类Sync来完成。
  public void run() {
        //调用Sync的对应方法
        sync.innerRun();
    }
     FutureTask.Sync.innerRun(),这样做的目的就是为了维护任务执行的状态,只有当执行完后才能够获得任务执行结果。在该方法中,首先设置执行状态为RUNNING只有判断任务的状态是运行状态,才调用任务内封装的回调,并且在执行完成后设置回调的返回值到FutureTask的result变量上。
注意:FutureTask.run方法是在线程池中被执行的,而非主线程
1、通过执行Callable任务的call方法;
2、如果call执行成功,则通过set方法保存结果;
3、如果call执行有异常,则通过setException保存异常;
void innerRun() {
            //通过对AQS的状态位state的判断来判断任务的状态是运行状态,则调用任务内封装的回调,并且设置回调的返回值
                if (getState() == RUNNING)
                    innerSet(callable.call()); 
        }

    void innerSet(V v) {
            for (;;) {
                int s = getState();
                //设置运行状态为完成,并且把回调额执行结果设置给result变量
                if (compareAndSetState(s, RAN)) {
                    result = v;
                    releaseShared(0);
                    done();
                    return;
                }
            }
至此工作线程执行Task就结束了。提交的任务是由Worker工作线程执行,正是在该线程上调用Task中定义的任务内容,即封装的Callable回调,并设置执行结果。

3.获取执行结果
     获取执行结果这个过程总是最容易的事情,只需调用FutureTask的get()方法即可。该方法是在Future接口中就定义的。get方法的作用就是等待执行结果。
     FutureTask的get方法同样委托给Sync来执行,依靠内部类Sync完成同步。
   public V get() throws InterruptedException, ExecutionException {
        return sync.innerGet();
   }

   V innerGet() throws InterruptedException, ExecutionException {
          //获得锁,表示执行完毕,才能获得后执行结果,否则阻塞等待执行完成再获取执行结果
            acquireSharedInterruptibly(0);
            return result;
        }
        protected int tryAcquireShared(int ignore) {
            return innerIsDone()? 1 : -1;
    }
     如果执行完成了则可以继续执行后面的代码,返回result结果,否则如果还未完成,则阻塞线程等待执行完成,谁调用get谁阻塞。调用FutureTask的get方法的线程,一般应该就是提交Task的线程,在例子中就是main线程,main调用get会被阻塞,main中get后面的代码System.out.println(outputs); 会等结果返回后再执行。

四.总结

1.外面需要提交任务的角色(如例子中老大的老大),首先创建一个任务执行服务ExecutorService,一般使用工具类Executors的若干个工厂方法 创建不同特征的线程池ThreadPoolExecutor,例子中是使用newFixedThreadPool方法创建有n个固定工作线程的线程池。
2.线程池是专门负责从外面接活的老大。把任务封装成一个FutureTask对象,并根据输入定义好要获得结果的类型,就可以submit任务了。
3.线程池就像我们团队里管人管项目的老大,各个都有一套娴熟、有效的办法来对付输入的任务和手下干活的兄弟一样,内部有一套比较完整、细致的任务管理办法,工作线程管理办法,以便应付输入的任务。这些逻辑全部在其execute方法中体现。
4.线程池接收输入的task,根据需要创建工作线程,启动工作线程来执行task。
5.工作线程在其run方法中一直循环,从任务队列领取可以执行的task,调用task的run方法执行task内定义的任务。
6.FutureTask的run方法中调用其内部类Sync的innerRun方法来执行封装的具体任务,并把任务的执行结果返回给FutureTask的result变量。
7.当提及任务的角色调用FutureTask的get方法获取执行结果时,Sync的innerGet方法被调用。根据任务的执行状态判断,任务执行完毕则返回执行结果;未执行完毕则等待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值