Executor 线程池框架详解

一、什么是Executor框架?

我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。


二、Executor框架结构图解

1、Executor框架包括3大部分:

  1. 任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
  2. 任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。
  3. 异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。

Executor框架的成员及其关系可以用一下的关系图表示:

 这些类和接口简介:

  • Executor:是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
  • ThreadPoolExecutor:是线程池的核心实现类,用来执行被提交的任务。
  • ScheduledThreadPoolExecutor:是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
  • Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
  • Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或Scheduled-ThreadPoolExecutor执行。

 

使用步骤:

1、创建Runnable并重写run()方法或者Callable对象并重写call()方法:

class callableTest implements Callable<String >{
            @Override
            public String call() {
                try{
                    String a = "return String";
                    return a;
                }
                catch(Exception e){
                    e.printStackTrace();
                    return "exception";
                }
            }
}

2、创建Executor接口的实现类ThreadPoolExecutor类或者ScheduledThreadPoolExecutor类的对象,然后调用其execute()方法或者submit()方法把工作任务添加到线程中,如果有返回值则返回Future对象。其中Callable对象有返回值,因此使用submit()方法;而Runnable可以使用execute()方法,此外还可以使用submit()方法,只要使用callable(Runnable task)或者callable(Runnable task,  Object result)方法把Runnable对象包装起来就可以,使用callable(Runnable task)方法返回的null,使用callable(Runnable task,  Object result)方法返回result。

ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,
                100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
Future<String> future = tpe.submit(new callableTest());

3、调用Future对象的get()方法后的返回值,或者调用Future对象的cancel()方法取消当前线程的执行。最后关闭线程池

try{
      System.out.println(future.get());
   }
   catch(Exception e){
            e.printStackTrace();
   }
   finally{
            tpe.shutdown();
   }

2、Executor框架成员

ThreadPoolExecutor实现类、ScheduledThreadPoolExecutor实现类、Future接口、Runnable和Callable接口、Executors工厂类


三、ThreadPoolExecutor详解

ThreadPoolExecutor是Executor接口的一个重要的实现类,是线程池的具体实现,用来执行被提交的任务。

主要由下面4个组件构成:

  • corePool:核心线程池的大小。
  • maximumPool:最大线程池的大小。
  • BlockingQueue:用来暂时保存任务的工作队列。
  • RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或已经饱和时(达到了最大线程池且工作队列已满),execute()方法将要调用的Handler
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
int corePoolSize,int maximumPoolSize,
long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);

ThreadPoolExecutor的三种类型

通过Executor框架的工具类Executors,可以创建(Executors.newXXX)3种类型的ThreadPoolExecutor:FixedThreadPool、SingleThreadExecutor、CachedThreadPool。

1、FixedThreadPool :可重用固定线程数的线程池

(适用于为了满足资源管理的需求,而需要适当限制当前线程数量的情景,适用于负载比较重的服务器。)

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

FixedThreadPool的executor()方法的运行示意图: 

  1. 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
  2. 在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。
  3. 当线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

【注】FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列。使用无界队列会对线程池带来如下影响:

  1. 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
  2. 使用无界队列时maximumPoolSize、keepAliveTime将是无效参数。
  3. 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒接任务。(不会调用RejectedExecutionHandler.rejectedExecution方法)

2、SingleThreadExecutor:只会创建一个线程执行任务

(适用于需要保证顺序执行各个任务;并且在任意时间点,没有多线程活动的场景。)

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
 
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

SingleThreadExecutor的executor()方法的运行示意图: 

  1. 如果当前运行的线程数少于corePoolSize(<1,即线程池中无运行的线程),则创建新线程来执行任务。
  2. 在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入LinkedBlockingQueue。
  3. 当线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

【注】SingleThreadExecutorl也使用无界队列LinkedBlockingQueue作为工作队列,对线程池带来的影响与FixedThreadPool相同。

3、CachedThreadPool :是一个会根据需要创建线程的线程池

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
 
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

(大小无界,适用于执行很多的短期异步任务的小程序,或负载较轻的服务器)

【注】CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源

  • 它是一个可以无限扩大的线程池
  • 它比较适合处理执行时间比较小的任务
  • corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大
  • keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死
  • 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程

CachedThreadPool的工作流程大概如下:

  • 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,executor()方法执行完成;否则执行下面的步骤2.
  • 当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下步骤1将失败。此时CachedThreadPool会创建一个新线程执行任务,executor方法执行完成。
  • 在步骤2中创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒。如果60秒内主线程提交了一个新任务,那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。

四、ScheduledThreadPoolExecutor的详解

ScheduledThreadPoolExecutor类继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。主要用于在给定的延迟后执行任务或者定期执行任务。作用类似于java.util包下的Timer类,但是比Timer功能更强大、更灵活,因为Timer只能控制单个线程延迟或定期执行,而ScheduledThreadPoolExecutor对应的是多个线程的后台线程。

1、ScheduledThreadPoolExecutor的创建:

可以利用Executors工厂类来创建两种ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor和SingleThreadScheduledExecutor.

ScheduledThreadPoolExecutor:适用于若干个(固定)线程延时或者定期执行任务,同时为了满足资源管理的需求而需要限制后台线程数量的场景。

ScheduledExecutorService stp = Executors.newScheduledThreadPool(int threadNums);
ScheduledExecutorService stp = Executors.newScheduledThreadPool(int threadNums, ThreadFactory threadFactory);

SingleThreadScheduledExecutor:适用于需要单个线程延时或者定期的执行任务,同时需要保证各个任务顺序执行的应用场景。

ScheduledExecutorService stse = Executors.newSingleThreadScheduledExecutor(int threadNums);
ScheduledExecutorService stp = Executors.newSingleThreadScheduledExecutor(int threadNums, ThreadFactory threadFactory);

2、ScheduledThreadPoolExecutor的实现

通过查看源码,可以发现ScheduledThreadPoolExecutor的实现主要是通过把任务封装为ScheduledFutureTask来实现。ScheduledThreadPoolExecutor通过它的scheduledAtFixedTime()方法或者scheduledWithFixedDelay()方法向阻塞队列添加一个实现了RunnableScheduledFutureTask接口的ScheduledFutureTask类对象。
ScheduledFutureTask主要包括3个成员变量:

private final long sequenceNumber;
private long time;
private final long period;

sequenceNumber:序列号,用于保存任务添加到阻塞队列的顺序;

time:用于保存该任务将要被执行的具体时间;

period:周期,用于保存任务执行的间隔周期;

此外,ScheduledTreadPoolExecutor的阻塞队列是用DelayQueue实现的,可以实现元素延时delayTime后才能获取元素,在ScheduledThreadPoolExecutor中,DelayQueue内部封装了一个PriorityQueue,来对任务进行排序,首先对time排序,time小的在前,如果time一样,则sequence小的在前,也就是说如果time一样,那么先被提交的任务先执行。
因为DelayQueue是一个无界的队列,因此线程池的maximumPoolSize是无效的。ScheduledThreadPoolExecutor的工作流程大致如下:

  1. 主线程把调用ScheduledFutureTask对象的scheduledAtFixedRate()或者scheduledWithFixedDelay()方法把Runnable或者Callable对象包装成ScheduledFutureTask对象添加到阻塞队列中;
  2. 线程池中的线程1从阻塞队列中获取到期的ScheduledFutureTask任务,并执行任务;(到期的意思就是当前时间大于time)
  3. 执行完任务后把成员变量time改为下次要执行任务的时间,然后把新的ScheduledFutureTask任务重新放入阻塞队列中;

五、深入理解Executor框架FutureTask类

一直以来都对FutureTask这个“Future”不理解,为什么叫做“未来的任务呢”?这个“Future”体现在哪里呢?现在终于明白,FutureTask的Future就源自于它的异步工作机制,如果我们在主线程中直接写一个函数来执行任务,这是同步的任务,也就是说必须要等这个函数返回以后我们才能继续做接下的事情,但是如果这个函数返回的结果对接下来的任务并没有意义,那么我们等在这里是很浪费时间的,而FutureTask就提供了这么一个异步的返回结果的机制,当执行一个FutureTask的时候,我们可以接着做别的任务,在将来的某个时间,FutureTask任务完成后会返回FutureTask对象来包装返回的结果,只要调用这个对象的get()方法即可获取返回值。

当然多线程中继承ThreadPoolExecutor和实现Runnable也可以实现异步工作机制,可是他们没有返回值。这时可以使用FutureTask包装Runnable或者Callable对象,再使用FutureTask来执行任务。

Future接口和其唯一的实现类FutureTask类一般用于表示异步计算的结果。Future接口下提供方法来检查计算是否完成,等待其完成,并检索计算结果。 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪。 取消由cancel方法执行,isCancelled方法用于检测计算是否被取消,isDone方法用于检测计算是否完成。 提供其他方法来确定任务是否正常完成或被取消。

1、FutureTask的使用

FutureTask对象可以处于一下3种状态:

  1. 未启动:创建了一个FutureTask对象但没有执行futureTask.run();
  2. 已启动:futureTask.run()方法被执行的过程中;
  3. 已完成:futureTask.run()正常执行结束,或者futureTask被取消(futureTask.cancel()),或者执行futureTask.run()时抛出异常而异常结束;

FutureTask执行完后结果的获取  :futureTask.get( )

  1. 在已启动的状态调用futureTask.get( )将导致调用线程阻塞,直到FutureTask执行完毕,然后得到返回的FutureTask对象,调用futureTask.get( )获得任务返回值;
  2. 在已完成状态调用futureTask.get( ),将导致调用线程立即返回(正常完成,得到FutureTask对象)或者抛出异常(被取消或者因异常而结束)

futureTask被取消:futureTask.cancel( )

1、在未启动状态调用futureTask.cancel( )会导致该任务永远不会再执行;

2、在已启动状态:

  • 调用futureTask.cancel(true)会以中断的方式尝试停止任务,如果该任务不响应中断则无法停止;
  • 调用futureTask.cancel(false)将不会对正在执行的线程产生影响,也就是已启动的线程会让他执行完毕;

3、在已完成状态调用futureTask.cancel( )则会返回false;

 FutureTask的启动:

FutureTask实现了Future接口和Runnable接口,因此FutureTask对象的执行有两种方式:

1、交给线程池的Execute或submit方法执行;

import java.util.concurrent.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
 
class test{
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
        //用FutureTask包装Runnable或者Callable对象
        FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() {
                try{
                    String a = "return String";
                    return a;
                }
                catch(Exception e){
                    e.printStackTrace();
                    return "exception";
                }
            }
        });
        //交给线程池的Execute或submit方法执行
        tpe.submit(future);
        try{
            System.out.println(future.get());
        }
        catch(Exception e){
            e.printStackTrace();
        }
        finally{
            tpe.shutdown();
        }
    }
}

2、由调用线程直接执行:在调用线程中执行futureTask.run()方法;

把上述代码的  tpe.submit(future);   替换为   future.run(); 

2、FutureTask的实现

FutureTask是一个基于AQS同步队列实现的一个自定义同步组件,通过对同步状态state的竞争实现acquire或者release操作。

FutureTask的内部类Sync实现了AQS接口,通过对tryAcquire等抽象方法的重写和模板方法的调用来实现内部类Sync的tryAcquireShared等方法,然后聚合Sync的方法来实现FutureTask的get,cancel等方法;

FutureTask的get方法最终会调用AQS.acquireSharedInterruptibly方法,这个方法操作成功的条件是同步状态为RAN或者CANCELLED,也就是说如果这个FutureTask有线程E正在执行,那么这个FutureTask的状态是RUN,因此AQS.acquireSharedInterruptibly方法调用失败,此时调用get方法的线程被阻塞,添加到等待队列中(如下图线程D,其中A,B,C是已经被阻塞添加到等待队列中的线程)。当前面执行FutureTask的线程E执行完毕,那么以原子方式更新同步状态state的值为RAN,并执行AQS.release方法,然后唤醒等待队列中的第一个节点中的线程A,此时线程A出队列获得同步状态,并原子设置state为RUN,当线程A执行完毕,把state原子更新为RUN,然后唤醒线程B,以此类推,因此一颗看出对于一个FutureTask,同一时间只有一个线程执行这个任务。

3、FutureTask使用场景

1、FutureTask执行多任务计算的使用场景,利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。

public class FutureTaskForMultiCompute {

    public static void main(String[] args) {

        FutureTaskForMultiCompute inst=new FutureTaskForMultiCompute();
        // 创建任务集合
        List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
        // 创建线程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            // 传入Callable对象创建FutureTask对象
            FutureTask<Integer> ft = new FutureTask<Integer>(inst.new ComputeTask(i, ""+i));
            taskList.add(ft);
            // 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;
            exec.submit(ft);
        }

        System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");

        // 开始统计各计算线程计算结果
        Integer totalResult = 0;
        for (FutureTask<Integer> ft : taskList) {
            try {
                //FutureTask的get方法会自动阻塞,直到获取计算结果为止
                totalResult = totalResult + ft.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        // 关闭线程池
        exec.shutdown();
        System.out.println("多任务计算后的总结果是:" + totalResult);

    }

    private class ComputeTask implements Callable<Integer> {

        private Integer result = 0;
        private String taskName = "";

        public ComputeTask(Integer iniResult, String taskName){
            result = iniResult;
            this.taskName = taskName;
            System.out.println("生成子线程计算任务: "+taskName);
        }

        public String getTaskName(){
            return this.taskName;
        }

        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub

            for (int i = 0; i < 100; i++) {
                result =+ i;
            }
            // 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果是等待直至完成。
            Thread.sleep(5000);
            System.out.println("子线程计算任务: "+taskName+" 执行完成!");
            return result;
        }
    }
}

2、FutureTask在高并发环境下确保任务只执行一次


总结线程池:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值