四十一、并发编程之Java的Executor框架实现原理

本文详细解析Java的Executor框架,包括Executor、ExecutorService接口,以及Executors的静态方法。探讨了Runnable、Callable、Future接口的使用,FutureTask的实现,和线程池执行原理,如ThreadPoolExecutor的构造及线程池的关闭。
摘要由CSDN通过智能技术生成

一、Java的Executor框架

在这里插入图片描述

二、Executor接口

 	public interface Executor {  
         void execute(Runnable command);  
    }  

Executor接口是Executor框架中最基础的部分,定义了一个用于执行Runnable的execute方法,它没有实现类只有另一个重要的子接口ExecutorService。

三、ExecutorService接口

  	  //继承自Executor接口  
      public interface ExecutorService extends Executor {  
		 //关闭方法,调用后执行之前提交的任务,不再接受新的任务   
         void shutdown();  
         //从语义上可以看出是立即停止的意思,将暂停所有等待处理的任务并返回这些任务的列表 
         List<Runnable> shutdownNow();  
         //判断执行器是否已经关闭 
         boolean isShutdown();  
         //关闭后所有任务是否都已完成 
         boolean isTerminated();  
         //中断 
         boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;  
         //提交一个Callable任务 
         <T> Future<T> submit(Callable<T> task);  
         //提交一个Runable任务,result要返回的结果 
         <T> Future<T> submit(Runnable task, T result);  
         //提交一个Runable任务 
         Future<?> submit(Runnable task);  
		 //执行所有给定的任务,当所有任务完成,返回保持任务状态和结果的Future列表 
         <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)  
             throws InterruptedException;  
     	//执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的Future列表。 
         <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,  
                                       long timeout, TimeUnit unit)  
             throws InterruptedException;  
         //执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。 
         <T> T invokeAny(Collection<? extends Callable<T>> tasks)  
             throws InterruptedException, ExecutionException;  
      	//执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。 
         <T> T invokeAny(Collection<? extends Callable<T>> tasks,  
                         long timeout, TimeUnit unit)  
             throws InterruptedException, ExecutionException, TimeoutException;  
     }  

ExecutorService接口继承自Executor接口,定义了终止、提交,执行任务、跟踪任务返回结果等方法:

  • execute(Runnable command):履行Ruannable类型的任务。
  • submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象。
  • shutdown():在完成已提交的任务后封闭办事,不再接管新任务。
  • shutdownNow():停止所有正在履行的任务并封闭办事。
  • isTerminated():测试是否所有任务都履行完毕了。
  • isShutdown():测试是否该ExecutorService已被关闭。

四、Executors的静态方法

负责生成各种类型的ExecutorService线程池实例:

  • newFixedThreadPool(numberOfThreads:int):(固定线程池)ExecutorService 创建一个固定线程数量的线程池,并行执行的线程数量不变,线程当前任务完成后,可以被重用执行另一个任务。
  • newCachedThreadPool():(可缓存线程池)ExecutorService 创建一个线程池,按需创建新线程,就是有任务时才创建,空闲线程保存60s,当前面创建的线程可用时,则重用它们。
  • new SingleThreadExecutor();(单线程执行器)线程池中只有一个线程,依次执行任务。
  • new ScheduledThreadPool():线程池按时间计划来执行任务,允许用户设定执行任务的时间。
  • new SingleThreadScheduledExcutor();线程池中只有一个线程,它按规定时间来执行任务。

五、Runnable、Callable、Future接口

1、Runnable接口

	// 实现Runnable接口的类将被Thread执行,表示一个基本的任务  
      public interface Runnable {  
          // run方法就是它所有的内容,就是实际执行的任务  
          public abstract void run();  
      }  

2、Callable接口

与Runnable接口的区别在于它接收泛型,同时它执行任务后带有返回内容

	// Callable同样是任务,与Runnable接口的区别在于它接收泛型,同时它执行任务后带有返回内容  
      public interface Callable<V> {  
          // 相对于run方法的带有返回值的call方法  
          V call() throws Exception;  
    }  

Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor和ScheduledThreadPoolExecutor执行,他们之间的区别是Runnable不会返回结果,而Callable可以返回结果。

Executors可以把一个Runnable对象转换成Callable对象:

 public static Callable<Object> callable(Runnbale task);  

Executors把一个Runnable和一个待返回的结果包装成一个Callable的API:

  public static<T> Callable<T> callable(Runnbale task,T result);  

当把一个Callable对象(Callable1,Callable2)提交给ThreadPoolExecutor和ScheduledThreadPoolExecutor执行时,submit(…)会向我们返回一个FutureTask对象。我们执行FutureTask.get()来等待任务执行完成,当任务完成后,FutureTask.get()将返回任务的结果。

3、 Future接口

	  // Future代表异步任务的执行结果  
      public interface Future<V> {          
         //尝试取消一个任务,如果这个任务不能被取消(通常是因为已经执行完了),返回false,否则返回true。   
         boolean cancel(boolean mayInterruptIfRunning);  
         //返回代表的任务是否在完成之前被取消了 
         boolean isCancelled();  
 		 //如果任务已经完成,返回true 
         boolean isDone();    
         //获取异步任务的执行结果(如果任务没执行完将等待) 
         V get() throws InterruptedException, ExecutionException;  
         //获取异步任务的执行结果(有最常等待时间的限制) 
         //timeout表示等待的时间,unit是它时间单位 
         V get(long timeout, TimeUnit unit)  
             throws InterruptedException, ExecutionException, TimeoutException;  
     }  

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

在Future接口中声明了5个方法,下面依次解释每个方法的作用:

  • cancel方法:用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • isCancelled方法:表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone方法:表示任务是否已经完成,若任务完成,则返回true;
  • get()方法:用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

也就是说Future提供了三种功能:

  • 判断任务是否完成;
  • 能够中断任务;
  • 能够获取任务执行结果。

六、FutureTask

通常使用FutureTask来处理我们的任务。FutureTask类同时又实现了Runnable接口,所以可以直接提交给Executor执行。
FutureTask提供了2个构造器:

  	//事实上,FutureTask是Future接口的一个唯一实现类。  
 	public FutureTask(Callable<V> callable) {  
    }  
    public FutureTask(Runnable runnable, V result) {  
    }   

使用FutureTask实现超时执行的代码如下:

    ExecutorService executor = Executors.newSingleThreadExecutor();     
    FutureTask<String> future =     
           new FutureTask<String>(new Callable<String>() {//使用Callable接口作为构造参数     
             public String call() {     
               //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型     
           }});     
    executor.execute(future);     
    //在这里可以做别的任何事情     
    try { 
    	//取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果         
        result = future.get(5000, TimeUnit.MILLISECONDS); 
    } catch (InterruptedException e) {     
        futureTask.cancel(true);     
    } catch (ExecutionException e) {     
        futureTask.cancel(true);     
    } catch (TimeoutException e) {     
        futureTask.cancel(true);     
    } finally {     
        executor.shutdown();     
    }    

不直接构造Future对象,也可以使用ExecutorService.submit方法来获得Future对象,submit方法即支持以 Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性。使用示例如下:

    ExecutorService executor = Executors.newSingleThreadExecutor();     
    FutureTask<String> future = executor.submit(     
       new Callable<String>() {//使用Callable接口作为构造参数     
           public String call() {     
          //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型     
       }});     
    //在这里可以做别的任何事情     
    //同上面取得结果的代码   

七、线程池实现原理详解

1、ThreadPoolExecutor是线程池的实现类

//构造方法
public ThreadPoolExecutor(
	int corePoolSize, // corePoolSize(线程池的基本大小)  
    int maximumPoolSize, //maximumPoolSize(线程池最大大小) 
    long keepAliveTime, //keepAliveTime(线程活动保持时间)
    TimeUnit unit, //TimeUnit(线程活动保持时间的单位)   
    BlockingQueue<Runnable> workQueue, //workQueue(任务队列)    
    ThreadFactory threadFactory,  //threadFactory:用于设置创建线程的工厂  
    RejectedExecutionHandler handler) //handler(饱和策略):表示当拒绝处理任务时的策略 

2、Executors创建线程池

我们尽量优先使用Executors提供的静态方法来创建线程池,如果Executors提供的方法无法满足要求,再自己通过ThreadPoolExecutor类来创建线程池 。

	Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池         
    Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE  
    Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池  
1.下面是这三个静态方法的具体实现:
  • newFixedThreadPool:(固定线程池)
 public static ExecutorService newFixedThreadPool(int nThreads) {  
        return new ThreadPoolExecutor(nThreads, nThreads,  
                                      0L, TimeUnit.MILLISECONDS,  
                                      new LinkedBlockingQueue<Runnable>());  
    }  

线程池corePoolSize和maximumPoolSize值是相等的(n,n),把keepAliveTime设置0L,意味着多余的空闲线程会被立即终止。
newFixedThreadPool的execute方法执行过程:
1)如果当前运行线程数少于corePoolSize,则创建新线程来执行任务(优先满足核心池)
2)当前运行线程数等于corePoolSize时,将任务加入LinkedBlockingQueue链式阻塞队列(核心池满了在进入队列)
3)当线程池的任务完成之后,循环反复从LinkedBlockingQueue队列中获取任务来执行

  • newSingleThreadExecutor:(单线程执行器)
    newSingleThreadExecutor是使用单个worker线程的Executors.
 public static ExecutorService newSingleThreadExecutor() {  
        return new FinalizableDelegatedExecutorService  
            (new ThreadPoolExecutor(1, 1,  
                                    0L, TimeUnit.MILLISECONDS,  
                                    new LinkedBlockingQueue<Runnable>()));  
    }  

newSingleThreadExecutor的execute方法执行过程如下:
1)当前运行的线程数少于corePoolSize(即当前线程池中午运行的线程),则创建一个新的线程来执行任务
2)当线程池中有一个运行的线程时,将任务加入阻塞队列
3)当线程完成任务时,会无限反复从链式阻塞队列中获取任务来执行

  • newCachedThreadPool:可缓存线程池
public static ExecutorService newCachedThreadPool() {  
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                                      60L, TimeUnit.SECONDS,  
                                      new SynchronousQueue<Runnable>());  
    }  

newCachedThreadPool是一个根据需要创建线程的线程池。
newCachedThreadPool的corePoolSize设置0,即核心池是空,maxmumPoolSize设置为Integer.MAX_VALUE,即maxmumPool是无界的。keepAliveTime设置60L,当空闲线程等待新任务最长时间是60s,超过60s就终止

从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了:

  • newFixedThreadPool:创建的线程池corePoolSize和maximumPoolSize值是相等的(n,n),它使用的LinkedBlockingQueue;
  • newSingleThreadExecutor:将corePoolSize和maximumPoolSize都设置为1(1,1),也使用的LinkedBlockingQueue;
  • newCachedThreadPool:将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

2.三个线程池的特点:
  • 1.newFixedThreadPool:创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数corePoolSize,则将提交的任务存入到池队列中。
  • 2.newCachedThreadPool:创建一个可缓存的线程池。这种类型的线程池特点是:
    1)工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
    2)如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
  • 3.newSingleThreadExecutor:创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的

3、线程池执行示意图:

在这里插入图片描述

  • 首先线程池判断基本线程池是否已满(< corePoolSize ?)?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
  • 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
  • 最后线程池判断整个线程池是否已满(< maximumPoolSize ?)?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

总结:线程池优先要创建出基本线程池大小(corePoolSize)的线程数量,没有达到这个数量时,每次提交新任务都会直接创建一个新线程,当达到了基本线程数量后,又有新任务到达,优先放入等待队列,如果队列满了,才去创建新的线程(不能超过线程池的最大数maxmumPoolSize)

4、向线程池提交任务的两种方式

  • 通过execute()方法
ExecutorService threadpool= Executors.newFixedThreadPool(10);    
    threadpool.execute(new Runnable(){...});  

这种方式提交没有返回值,也就不能判断任务是否被线程池执行成功。

  • 通过submit()方法
 Future<?> future = threadpool.submit(new Runnable(){...});    
        try {    
                Object res = future.get();//获取任务执行结果    
            } catch (InterruptedException e) {    
                // 处理中断异常    
                e.printStackTrace();    
            } catch (ExecutionException e) {    
                // 处理无法执行任务异常    
                e.printStackTrace();    
            }finally{    
                // 关闭线程池    
                executor.shutdown();    
            }    

使用submit 方法来提交任务,它会返回一个Future对象,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。

5、线程池的关闭:

  • shutdown():不会立即终止线程池,而是再也不会接受新的任务,要等所有任务缓存队列中的任务都执行完后才终止
  • shutdownNow():立即终止线程池,再也不会接受新的任务,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

线程池本身的状态

 volatile int runState;     
    static final int RUNNING = 0;   //运行状态  
    static final int SHUTDOWN = 1;   //关闭状态  
    static final int STOP = 2;       //停止  
    static final int TERMINATED = 3; //终止,终结  
  • 当创建线程池后,初始时,线程池处于RUNNING状态;
  • 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕,最后终止;
  • 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务,返回没有执行的任务列表;
  • 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

原文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值