创建一个线程执行者

使用 Executor 框架的第一步是创建一个 ThreadPoolExecutor 类对象。 您可使用此类提供的4个构造函数或使用一个名为Executors的工厂类。 一旦您有了一个 executor,您可将 Runnable 或 Callable 对象发送给它去执行。

任务

模拟一个 web 服务器处理来自不同客户端的请求。

实现

  • 线程类 : Task

    • 属性 initDate : Date 类型, 保存任务创建的时间
    • 属性 name : 任务的名称
    • 构造函数 : 将名称初始化为参数值; 将任务创建时间初始化为当前时间
        private Date initDate;
        private String name;
    
        public Task(String name) {
            initDate = new Date();
            this.name = name;
        }
    
    • run方法
      1. 输出任务开始时间到终端
      2. 随机睡上一段时间
      3. 输出任务结束时间到终端
            System.out.printf("%s: Task %s: Created on: %s\n", Thread.currentThread().getName(), name, initDate);
            System.out.printf("%s: Task %s: Started on: %s\n", Thread.currentThread().getName(), name, new Date());
    
            try {
                Long duration = (long) (Math.random() * 10);
                System.out.printf("%s: Task %s: Doing a task during %d seconds\n", Thread.currentThread().getName(), name, duration);
                TimeUnit.SECONDS.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());
    
  • 线程类 : Server : 使用一个执行者来执行每一个任务

    • 属性 executor : ThreadPoolExecutor
    • 构造函数: 使用 Executors 类初始化 ThreadPoolExecutor 对象
        private ThreadPoolExecutor executor;
    
        public Server() {
            executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();
        }
    
    • executeTask() 方法 : 接受一个 Task 对象作为参数并将其发送给 executor
      1. 输出信息到终端表示一个新任务已到达
      2. 调用 executor 的 execute() 方法发送任务到 executor
      3. 输出一些 executor 的状态信息到终端
        public void executeTask(Task task) {
            System.out.printf("Server: A new task has arrived\n");
            executor.execute(task);
            System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
            System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
            System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
        }
    
    • endServer() 方法 : 在此方法中, 调用 executor 的 shutdown() 方法结束其执行.
        public void endServer() {
            executor.shutdown();
        }
    
  • 控制类 : Main

    • 创建 100 个 task 给 server 执行
        Server server = new Server();
        for (int i = 0; i < 100; i++) {
            Task task = new Task("Task " + i);
            server.executeTask(task);
        }
        server.endServer();
    

工作原理

本例的关键是 Server 类.此类创建并使用 ThreadPoolExecutor 来执行任务.

第一个重要的地方是在 Server 类的构造函数中 ThreadPoolExecutor 的创建。 ThreadPoolExecutor 类有4个不同的构造函数,但是因为它们的复杂性,Java concurrency API提供了 Executors 类来构造执行者及其他相关对象. 尽管能直接使用其中一个构造函数来创建 ThreadPoolExecutor,仍推荐使用 Executors 类.

在本例中,使用 newCachedThreadPool() 方法创建了一个线程池。此方法返回一个 ExecutorService 对象,能被转为 ThreadPoolExecutor 以执行它所有的方法。 创建的线程缓存池在需要时创建新的线程来执行新的任务,并重用已有的执行完任务、现在可用的线程。 线程的回收利用的好处是减少了线程创建的时间;不好的地方是总是有那么多线程准备执行新的任务。 如果发送过多的任务到 executor,系统可能过载。

  • 只在您有合理数量的线程或每个线程执行的时间很短时,才使用由 newCachedThreadPool() 方法创建的 executor。

一旦创建了 executor,就可用 execute() 方法发送 Runnable 或 Callable 类型的任务来执行。 (本例中发送的是实现 Runnable 接口的 Task 类对象)

您已输出了一些带有 executor 相关信息的日志信息。具体地说,您使用了以下方法:

  • getPoolSize(): 返回 executor 的池内线程的实际数目
  • getActiveCount(): 返回 executor 内在执行任务的线程数目
  • getCompletedTaskCount(): 返回已由 executor 执行完毕的任务数

ThreadPoolExecutor 类的一个重要的方面,也是执行者们的一个共同点,就是必须明确地结束它。 如果您不这么做,执行者将继续执行,程序不会结束。如果执行者没有任务执行,它继续等待新任务且不会结束。 一个 Java 应用不会结束直到它所有的非守护线程结束执行,所以,如果您不终止执行者,您的应用程序永远不会结束。

为了告知执行者您想结束它,可使用 ThreadPoolExecutor 类的 shutdown() 方法。 当执行者完成了所有待处理任务的执行,就结束它自己的执行。 调用 shutdown() 方法后,如果您尝试发送另一个任务给执行者,任务将被拒绝,执行者将抛出 RejectedExecutionException 异常。

了解更多

ThreadPoolExecutor 类提供了很多方法来获取它的状态信息。 本例中使用了 getPoolSize()、getActiveCount() 和 getCompletedTaskCount() 方法来获得关于线程池大小、线程数目和执行者已完成任务数。 也可使用 getLargestPoolSize() 方法返回每次在线程池内的最大线程数目。

ThreadPoolExecutor 类也提供了与执行者的结束有关的其他方法。如下:

  • shutdownNow() : 此方法立即停下此执行者。
    • 不执行待处理任务。返回所有待处理任务列表。
    • 当调用此方法时在运行中的任务继续执行,但不等待它们的结束。
  • isTerminated(): 在已调用了 shutdown() 或 shutdownNow() 方法且执行者已结束了停工处理时,此方法返回true.
  • isShutdown(): 如果已经调用了执行者的 shutdown() 方法,此方法返回 true
  • awaitTermination(long timeout, TimeUnit unit): 此方法阻塞调用线程直到执行者的任务已结束或超过指定时间。
    • note: 如果想等待任务的结束,不管任务的持续时间,可用一个大的超时时间,例如单位是天