Executor框架

线程池简化了线程的管理工作,并且java.util.concurrent提供了一种灵活的线程池实现作为Executor框架的一部分。在java类库中,任务执行的主要抽象不是Thread,而是Executor

Executor接口

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

虽然Executor是一个简单的接口,但它却为灵活且强大的异步任务执行框架提供了基础,该框架能支持多种不同类型的任务执行策略。

它提供了一种标准的方法将任务的提交过程与执行过程解耦开来,并用Runnable来表示任务。

Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。

Executor基于生产者-消费者模式,提交任务的操作相当于生产者(生成待完成的工作单元),执行任务的线程则相当于消费者(执行完这些工作单元)。如果要在程序中实现一个生产者-消费者的设计,那么最简单的方式通常就是使用Executor。

Executor的使用

public class Demo {
    //线程的数量
    private static final int NTHREADS = 100;
    //生成Executor
    private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(80);
        while(true){
            Socket connection = serverSocket.accept();
            Runnable runnable = () -> {
                    handleRequest(connection);
            };
            //执行
            exec.execute(runnable);
        }

    }
}

执行策略

​ 通过将任务的提交与执行解耦开,从而可以轻易就可以为某种类型的任务指定和修改执行策略。

​ 在执行策略中定义任务的 What ,Where,when,How等方面,包括:

  • 在什么线程中执行任务?

  • 任务按照什么顺序执行?

  • 有多少个任务能并发执行?

  • 在队列中有多少个任务在等待执行?

  • 如果系统由于过载要拒绝一个任务,那么应该选择哪一个任务?另外如何通知应用程序有任务被拒绝?

  • 在执行一个任务之前或者之后,应该进行哪些动作?

线程池

​ 线程池与工作队列密切相关,其中在工作队列中保存了所有等待执行的任务。工作者线程的任务很简单:从工作队列中获取一个人物,执行任务,然后返回线程池并等待下一个任务。

​ “在线程池中执行任务”比“为每个任务分配一个线程”有时更多。

通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。

当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。

通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保持忙碌状态。

同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败

Executors工具类

newFixedThreadPool方法:newFixedThreadPool方法将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这是线程池的规模将不再变化。

newCachedThreadPool方法:newCachedThreadPool方法将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。

newSingleThreadExecutor方法:newSingleThreadExecutor方法创建的是一个单线程的Executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadExecutor能确保依照任务在队列中的顺序来穿行执行(按照FIFO,LIFO,优先级等方式)。

newScheduledThreadPool方法:会创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer

Executor的生命周期

我们知道了如何创建一个Executor,但并没有讨论如何关闭它。Executor的实现通常会创建线程来执行任务。但JVM只有在所有(非守护线程)线程全部终止后才会退出。因此,如果无法正确的关闭Executor,那么JVM将无法结束。

为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了一些用于生命周期管理的方法。

public interface ExecutorService extends Executor {

    //生命周期管理的方法
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    
    
    //用于任务提交的便利方法
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task); 
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <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的生命周期有3种状态:运行,关闭,已终止

  • ExecutorService在初始创建时处于运行状态。

  • shutdown方法将执行平缓的关闭过程:不再接受行的任务,同时等待已经提交的任务完成(包括还未开始执行的任务)。

  • shutdownNow方法将执行粗暴的关闭过程:它将尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务。

  • 在ExecutorService关闭后提交的任务将由“拒绝执行处理器”来处理,它会抛弃任务,或者使的execute方法抛出一个未检查的RejectedExecutionException。

  • 等所有任务都完成后,ExecutorService将转入终止状态。可以调用awaitTermination来等待ExecutorService到达终止状态,或者通过调用isTerminated来轮询ExecutorService是否已经终止。

  • 通常在调用awaitTermination之后会立即调用shutdown,从而产生同步的关闭ExecutorService的效果。