线程池原理分析(一)-线程池体系结构

概述

  随着摩尔定律失效,多核计算器成为主流,多线程提高执行效率就变得异常重要,而线程的创建销毁又是一个开销比较大的操作,于是就产生了线程池,把使用过的线程放入线程池中,重复利用,其思想就是这些,很简单,但是线程池的管理就没有那么简单了,首先要管理好多个线程,然后还要管理任务,所以整个事情就变得复杂起来,本篇先介绍一下线程池的继承结构图,简单介绍下各个接口和类的作用,下一篇文章再分析线程池。

线程池继承结构图

 简化结构图如下

 上面第一幅图把线程池的关键的类都画出来了,第二幅图是一个简化的图,这个里面的实现才是核心,第一幅图先不要管,先介绍第二幅图,当第二幅图介绍完之后,再回过头来看第一幅图。

Executor

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

  线程池是用来管理线程的,而线程是用来执行任务的,所以在最顶层设计了Executor接口,该接口就一个方法,用来执行任务。

ExecutorService

public interface ExecutorService extends Executor {
    
    //关闭线程池,但阻塞队列中的任务继续执行完之后才关闭
    void shutdown();
    //关闭线程池,阻塞队列中的任务直接丢弃
    List<Runnable> shutdownNow();
    //是否处于SHUTDOWN状态
    boolean isShutdown();
    //是否处于TERMINATED状态
    boolean isTerminated();
    //等待线程池进入TERMINATED状态
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    //提交任务,不过任务有返回值
    <T> Future<T> submit(Callable<T> task);
    //提交任务,不过任务有返回值
    <T> Future<T> submit(Runnable task, T result);
    //提交Runnable任务,会自动封装成Callable任务
    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;
  

}    

  这个接口主要作用就是提交任务,让接口Executor中的execute方法执行,开头的几个方法shutdow(),shutdownNow()等,这些都是让线程池结束的方法,但是线程池有好几个状态,而线程池结束就是在这些状态之间转换,具体到介绍ThreadPoolExecutor类的时候在介绍。

AbstractExecutorService

public abstract class AbstractExecutorService implements ExecutorService {

     //代码就不贴了
}

这个类是一个抽象类,实现了ExecutorService,主要是为该接口的方法提供一些默认的实现。

ThreadPoolExecutor

这个类就是线程池实现的核心类,线程池的线程管理和阻塞队列管理都是它完成的,下一篇文章会详细介绍这个类。

ScheduledExecutorService

public interface ScheduledExecutorService extends ExecutorService {
    //执行一次性任务,固定延迟之后开始执行,有返回结果
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    //一次性任务,有返回
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);
    //固定延迟之后开始执行,之后按照固定时间间隔执行,如果在间隔时间内上一次调度还没有执行完
   //不会影响下一次执行
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit); 
    //固定延迟之后开始执行,之后按照固定时间间隔执行,如果在间隔时间内上一次调度还没有执行完
   //下一次调度任务即便时间已经到了也不会执行,会等到上一次任务执行完之后在等待固定时间间隔之后执行
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

在ExecutorService接口中定义了很多的提交任务的方法,但是那些提交任务的方法都没有设置延迟执行的,这个接口定义了4个固定延时的方法,其中最后两个还可以定时执行。

ScheduledThreadPoolExecutor

  java.util.concurrent.ScheduledThreadPoolExecutor ,继承 ThreadPoolExecutor ,并且实现 ScheduledExecutorService 接口,是两者的集大成者,相当于提供了“延迟”和“周期执行”功能的 ThreadPoolExecutor 。

  这里简单提一下其设计思想,大家都知道线程池中有一个队列用于放任务,如果这个队列是一个特殊的队列,比如DelayedQueue(ScheduledThreadPoolExecutor中使用的队列和这个类似),这个队列可以控制队列中的节点延迟时间,如果队列中的节点没有到期,通过take等方法就无法获取到值,会阻塞,直到延迟时间结束,take才可以获取到,这样就可以做到定时执行。到这里可能有的胖友有疑问,如果从队列中取出来,那队列中不就没有这个任务了,那下次再定时执行怎么办,这个就是ScheduledFutureTask(这个类上面没有介绍,看名字大家应该可以猜到,和FutureTask作用差不多)的事情了,该类会重写run方法,在run方法中会再次将任务放入到队列中。

具体可以参考:【死磕Java并发】—–J.U.C之线程池:ScheduledThreadPoolExecutor

小结

  以上就是第二幅图相关接口和类的介绍,其中线程池和定时调度类没有详细介绍,只是大致提了一下,因为这两个类很复杂,后面专门写文章介绍。下面就看一下第一幅图多出来的部分的内容。

                                                                                   

Executors

静态工厂类,提供了 Executor、ExecutorService 、ScheduledExecutorService、ThreadFactory 、Callable 等类的静态工厂方法,通过这些工厂方法我们可以得到相对应的对象。举例:

ExecutorService service = Executors.newFixedThreadPool(10);

Future

public interface Future<V> {
    //取消当前任务
    boolean cancel(boolean mayInterruptIfRunning);
    //是否已经取消
    boolean isCancelled();
    //是否执行完成
    boolean isDone();
    //获取执行完成的结果,如果还没有执行完成就阻塞
    V get() throws InterruptedException, ExecutionException;
    //获取执行完成的结果,等待固定的时间,如果规定时间内还没有执行完,抛异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

  获取任务执行状态的一个类,当任务提交到线程池,需要获取任务执行的结果,如果线程池中堆积了很多的任务,那很多任务都无法及时的获取执行结果,这个时候就看用户的个人选择了,是使用get等待,还是直接取消该任务。

RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {

    void run();
}|

  该接口继承了Runnable,那实现这个接口的类就可以直接作为任务传入线程池执行,而该接口同时又实现了Future接口,这就说明还可以去获取任务的执行状态。

FutureTask

public class FutureTask<V> implements RunnableFuture<V> {

    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

   //省略部分代码

}

  这个类就实现了RunnableFuture,该类是一个核心类,如果要获取线程的执行结果,就是通过这个类获取的,该类的实现和AQS很类似,里面也是搞了一个阻塞队列,然后搞了一个state来管理状态。其实这个阻塞队列就是一个单链表,里面放的是要获取该任务执行结果的线程,如果该任务还没有执行完,那这些线程就无法获取到结果,就会放入到了链表中,state的作用就是存放任务的状态,上面贴出的代码中可以看出任务可以处于以上7种状态,多个线程都可以操作state,所以要通过CAS来加锁修改,其实思想和AQS很类似。后面会详细分析。

具体大家可以参考:FutureTask源码解析(2)——深入理解FutureTask

CompletionService

public interface CompletionService<V> {
    //提交任务
    Future<V> submit(Callable<V> task);
    //提交任务
    Future<V> submit(Runnable task, V result);
    //获取任务执行结果
    Future<V> take() throws InterruptedException;
    //获取任务执行结果
    Future<V> poll(); 
    //获取任务执行结果
    Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;

}

  这个接口主要作用其实就是为了弥补Future的不足,通过Future的get方法可以获取执行结果,这个确实没问题,思考一下如下场景,如果提交了很多的任务,然后就等待获取这些任务的执行结果,但是又不知道哪个任务先执行完,如果从头开始遍历,如果第一个任务一下子执行了2个小时,那就阻塞了2小时,但是在这个两小时中可能已经有很多别的任务执行完了,我们本可以先去处理那些已经执行完的任务,但是却白白等了2小时,当然了,对于有经验的胖友来说,可以遍历然后调用isDone()方法,没有完成就过,然后一直死循环,直到所有的线程执行完,这样子也行,只是不够优雅。

  大家如果仔细看上面接口的定义,会发现很多的方法好像是操作队列的方法,其实像take,poll就是操作队列的方法,具体的实现是这样的,CompletionService的实现类会在类中定义一个阻塞队列,当有任务执行完了,任务的执行结果就放入队列中,然后我们只需要调用take方法获取就可以了,当队列中为空的时候,就阻塞等待,这样实现起来就很优雅。

具体大家可以参考:CompletionService和ExecutorCompletionService详解

ExecutorCompletionService

public class ExecutorCompletionService<V> implements CompletionService<V> {
    private final Executor executor;
    private final AbstractExecutorService aes;
    private final BlockingQueue<Future<V>> completionQueue;
//省略部分代码
}

  这个类就是上面那个接口的实现类,里面有如下代码

 private final BlockingQueue<Future<V>> completionQueue;

  这个阻塞队列就是放执行完成结果的。具体的思想在上面介绍接口的时候已经介绍了,这里就不在赘述了。

Delayed

public interface Delayed extends Comparable<Delayed> {
    //获取延迟时间
    long getDelay(TimeUnit unit);
}

  该接口定义很简单,就只有一个方法。

ScheduledFuture

public interface ScheduledFuture<V> extends Delayed, Future<V> {
}

  这个接口从名称就可以看出来,就是实现延时执行,并且还要获取执行结果。

总结

上面笼统的把每个类和接口给简单介绍了一下,下面梳理一下。其实和线程池相关的类中有四个类是很核心的类,分别如下:

  • ThreadPoolExecutor:这个就不用多说了,实现线程池的,核心类

  • FutureTask:如果不用获取定时任务执行结果,直接使用Runnable提交任务就可以了,如果要获取执行结果就需要使用FutureTask提交任务
  • ExecutorCompletionService:该类组合了上面两个类,线程池的作用是用来执行任务,而执行的任务就是FutureTask,然后该类还使用了一个阻塞队列把已经执行完成的任务放入到队列,方便获取

  • ScheduledThreadPoolExecutor:该类是实现定时调度的核心类,其执行也是需要依赖线程池

从上面的介绍中大家可以看出,最后两个类都要依赖于线程池,所以ThreadPoolExecutor才是最核心的,下一篇文章就介绍这个类。上面的介绍有点乱,只是想把线程池相关的重要的类都给梳理下,有个整体印象,之后在学习的时候知道自己处在什么位置,还有哪些没有学习到。

                                                                             

 参考:

【死磕 Java 并发】—– J.U.C 之线程池:线程池的基础架构

【死磕Java并发】—–J.U.C之线程池:线程池的基础架构

jdk1.8源码

原文地址:https://www.cnblogs.com/gunduzi/p/13673877.html