线程执行器(二)

6. 在执行器中延时执行任务

  如果不想让任务马上被执行,而想让任务在过一段时间之后才被执行,或者任务能够被周期性地执行。为了达到这个目的,执行器框架提供了ScheduledThreadPoolExecutor类。

  下面我们将学习如何创建ScheduledThreadPoolExecutor执行器,以及如何使用它在经过一个给定的时间后开始执行任务。

1. 创建一个名为Task的类,并实现Callable接口,接口的泛型参数为String类型。

import java.util.Date;
import java.util.concurrent.Callable;

public class Task implements Callable<String> {
    private String name;
    
    public Task(String name){
        this.name = name;
    }
    
    @Override
    public String call() throws Exception {
        System.out.printf("%s: Starting at : %s\n", name, new Date());
        return "Hello, world";
    }
    

}

2. 实现范例的主类Main,并实现main()方法。

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
        System.out.printf("Main: Starting at: %s\n", new Date());
        for(int i=0;i<5;i++){
            Task task = new Task("Task "+i);
            executor.schedule(task, i+1, TimeUnit.SECONDS);
        }
        //结束执行器
        executor.shutdown();
        //等待所有的任务执行结束
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.printf("Main: Ends at: %s\n", new Date());
    }
}

3. 程序运行结果如下

Main: Starting at: Sun Oct 25 15:51:23 CST 2015
Task 0: Starting at : Sun Oct 25 15:51:24 CST 2015
Task 1: Starting at : Sun Oct 25 15:51:25 CST 2015
Task 2: Starting at : Sun Oct 25 15:51:26 CST 2015
Task 3: Starting at : Sun Oct 25 15:51:27 CST 2015
Task 4: Starting at : Sun Oct 25 15:51:28 CST 2015
Main: Ends at: Sun Oct 25 15:51:28 CST 2015

  也可以使用Runnable接口来实现任务,因为ScheduledThreadPoolExecutor类的schedule()方法可以同时接受这两种类型的任务。

  虽然ScheduledThreadPoolExecutor是ThreadPoolExecutor类的子类,因为继承了ThreadPoolExecutor类所有的特性。但是,Java推荐仅在开发定时任务程序时采用ScheduledThreadPoolExecutor类。

  在调用shutdown()方法而仍有待处理的任务需要执行时,可以配置ScheduledThreadPoolExecutor的行为。默认的行为是不论执行器是否结束,待处理的任务仍将被执行。但是,通过调用ScheduledThreadPoolExecutor类的setExecuteExistingDelayedTasksAfterShutdownPolicy()方法则可以改变这个行为。传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。

7. 在执行器中周期性执行任务

  当发送一个任务给ThreadPoolExecutor类执行器后,根据执行器的配置,它将尽快地执行这个任务。当任务执行结束后,这个任务就会从执行器中删除;如果想再次执行这个任务,则需要再次发送这个任务到执行器。

  但是,执行器框架提供了ScheduledThreadPoolExecutor类来执行周期性的任务。

  下面我们将学习如何使用这个类的功能来计划执行周期性的任务。

1. 创建一个名为Task的类,并实现Runnable接口。

import java.util.Date;

public class Task implements Runnable {
    private String name;
    
    public Task(String name){
        this.name = name;
    }
    
    @Override
    public void run() {
        System.out.printf("%s: Starting at : %s\n", name, new Date());
    }
    
}

2. 实现范例的主类Main,并实现main()方法。

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        System.out.printf("Main: Starting at: %s\n", new Date());
        Task task = new Task("Task");
        //第一个参数为周期性执行的任务,第二个为第一次执行后延时时间,第三个为两次执行的时间周期,第四个为时间单位
        ScheduledFuture<?> result = executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
        try {
            for(int i=0;i<10;i++){
                System.out.printf("Main: Delay: %d\n", result.getDelay(TimeUnit.MILLISECONDS));
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //结束执行器
        executor.shutdown();
        //将线程休眠5秒,等待周期性的任务全部执行完成
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.printf("Main: Finished at: %s\n", new Date());
    }
}

3. 程序运行结果如下

Main: Starting at: Sun Oct 25 16:25:23 CST 2015
Main: Delay: 999
Main: Delay: 499
Main: Delay: 0
Task: Starting at : Sun Oct 25 16:25:24 CST 2015
Main: Delay: 1499
Main: Delay: 999
Main: Delay: 499
Main: Delay: 0
Task: Starting at : Sun Oct 25 16:25:26 CST 2015
Main: Delay: 1499
Main: Delay: 999
Main: Delay: 499
Task: Starting at : Sun Oct 25 16:25:28 CST 2015
Main: Finished at: Sun Oct 25 16:25:33 CST 2015

   ScheduledThreadPoolExecutor还提供了其他方法来安排周期性任务的运行,比如scheduleWithFixedRate()方法。这个方法与scheduleAtFixedRate()方法具有相同的参数,但是略有一些不同需要注意。在scheduleAtFixedRate()方法中,第3个参数表示任务两次开始时间的间隔,而在scheduleWithFixedRate()方法中,第3个参数表示任务上一次执行结束的时间与下一次开始执行的时间的间隔

  也可以配置ScheduledThreadPoolExecutor实现shutdown()方法的行为,默认行为是当调用shutdown()方法后,定时任务就结束了。可以通过ScheduledThreadPoolExecutor类的setContinueExistingPeriodicTasksAfterShutdownPolicy()方法来改变这个行为,传递参数为true给这个方法,这样调用shutdown()方法后,周期性任务仍将继续执行。

 8. 在执行器中取消任务

  有时候,我们可能需要取消已经发送给执行器的任务。在这种情况下,可以使用Future接口的cancel()方法来执行取消操作。

  下面我们将学习如何使用这个方法取消已经发送给执行器的任务。

1. 创建一个名为Task的类。

import java.util.concurrent.Callable;

public class Task implements Callable<String> {

    @Override
    public String call() throws Exception {
        while(true){
            System.out.printf("Task: Test\n");
            Thread.sleep(100);
        }
    }

}

2. 实现范例的主类Mian,并实现main()方法。

import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        Task task = new Task();
        System.out.println("Main: Executing the Task");
        Future<String> result = executor.submit(task);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Main: Canceling the Task");
        //取消任务
        result.cancel(true);
        System.out.println("Main: Cancelled: "+ result.isCancelled());
        System.out.println("Main: Done: "+ result.isDone());
        executor.shutdown();
        System.out.println("Main: The executor has finished");
    }
}

3. 程序运行结果如下

Main: Executing the Task
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Task: Test
Main: Canceling the Task
Main: Cancelled: true
Main: Done: true
Main: The executor has finished
View Code

  如果想取消一个已经发送给执行器的任务,可以使用Future接口的cancel()方法。根据调用cancel()方法时所传递的参数以及任务的状态,这个方法的行为有些不同。

  • 如果任务已经完成,或者之前已经被取消,或者由于某种原因而不能被取消,那么方法将返回false并且任务也不能取消。
  • 如果任务在执行器中等待分配Thread对象来执行它,那么任务被取消,并且不会开始执行。如果任务已经在运行,那么它依赖于调用cancel()方法时所传递的参数。如果传递的参数为true并且任务正在运行,那么任务将被取消。如果传递的参数为false并且任务正在运行,那么任务不会被取消。

  如果Future对象所控制任务已经被取消,那么使用Future对象的get()方法时将抛出CancellationException异常。

 9. 在执行器中控制任务的完成

  FutureTask类提供了一个名为done()的方法,允许在执行器任务执行结束之后,还可以执行一些代码。这个方法可以被用来执行一些后期处理操作,比如:产生报表,通过邮件发送结果或者释放一些系统资源。当任务执行完成是受FutureTask类控制时,这个方法在内部被FutureTask类调用。在任务结果设置后以及任务的状态已改变为isDone之后,无论任务是否被取消或者正常结束,done()方法都被调用。

  默认情况下,done()方法的实现为空,即没有任何具体的代码实现。我们可以覆盖FutureTask类并实现done()方法来改变这种行为。

  下面,我们将学习如何覆盖这些方法,并在任务结束后执行这些代码。

1. 创建名为ExecutableTask的类,并实现Callable接口,接口的泛型为String类型。

 

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class ExecutableTask implements Callable<String> {
    private String name;
    
    public ExecutableTask(String name){
        this.name = name;
    }
    
    @Override
    public String call() throws Exception {
        long duration = (long) (Math.random()*10);
        System.out.printf("%s: Waiting %d seconds for results.\n", name, duration);
        try {
            TimeUnit.SECONDS.sleep(duration);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "Hello, world. I'm "+name;
    }

    public String getName() {
        return name;
    }

}

2. 实现一个名为ResultTask的类,并继承FutureTask类。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ResultTask extends FutureTask<String> {
    private String name;
    public ResultTask(Callable<String> callable) {
        super(callable);
        this.name = ((ExecutableTask)callable).getName();
    }
    
    @Override
    protected void done() {
        if(isCancelled()){
            System.out.printf("%s: Has been canceled\n", name);
        }else{
            System.out.printf("%s: Has finished\n", name);
        }
    }
    
}

3. 实现范例的主类Main,并实现main()方法。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ExecutorService executor =  Executors.newCachedThreadPool();
        ResultTask[] resultTasks = new ResultTask[5];
        for(int i=0;i<5;i++){
            ExecutableTask executableTask = new ExecutableTask("Task"+i);
            resultTasks[i] = new ResultTask(executableTask);
            executor.submit(resultTasks[i]);
        }
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //取消已经发送给执行器的所有任务
        for(int i=0;i<resultTasks.length;i++){
            resultTasks[i].cancel(true);
        }
        //在控制台打印出没有被取消的任务的结果
        try {
            for(int i=0;i<resultTasks.length;i++){
                if(!resultTasks[i].isCancelled())
                    System.out.printf("%s\n", resultTasks[i].get());
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        executor.shutdown();
    }
}

4. 程序运行结果如下

Task0: Waiting 0 seconds for results.
Task0: Has finished
Task1: Waiting 2 seconds for results.
Task4: Waiting 5 seconds for results.
Task2: Waiting 5 seconds for results.
Task3: Waiting 4 seconds for results.
Task1: Has finished
Task3: Has finished
Task2: Has been canceled
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360)
    at ExecutableTask.call(ExecutableTask.java:16)
    at ExecutableTask.call(ExecutableTask.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Task4: Has been canceled
Hello, world. I'm Task0
Hello, world. I'm Task1
Hello, world. I'm Task3
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360)
    at ExecutableTask.call(ExecutableTask.java:16)
    at ExecutableTask.call(ExecutableTask.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
View Code

10. 在执行器中分离任务的启动与结果的处理

   通常情况下,使用执行器来执行并发任务时,将Runnable或Callable任务发送给执行器,并获得Future对象来控制任务。此外,还会碰到如下情形,需要在一个对象里发送任务给执行器,然后在另一个对象里处理结果。对于这种情况,Java并发API提供了CompletionService类。

  CompletionService类有一个方法用来发送任务给执行器,还有一个方法为下一个已经执行结束的任务获取Future对象。从内部实现机制来看,CompletionService类使用Executor对象来执行任务。这个行为的优势是可以共享CompletionService对象,并发送任务到执行器,然后其它的对象可以处理任务的结果,第二个方法有一个不足之处,它只能为已经执行结束的任务获取Future对象,因此,这些Future对象只能被用来获取任务的结果。

  在下面,我们将学习如何使用CompletionService类,在执行器中来分离任务的启动与结果的处理。

1. 创建名称为ReportGenerator的类,并实现Callable接口,接口的泛型参数为String类型。

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class ReportGenerator implements Callable<String> {
    private String sender;
    private String title;
    
    public ReportGenerator(String sender, String title){
        this.sender = sender;
        this.title = title;
    }
    
    @Override
    public String call() throws Exception {
        long duration = (long) (Math.random()*10);
        System.out.printf("%s_%s: ReportGenerator: Generating a report during %d seconds\n",this.sender, this.title, duration);
        try {
            TimeUnit.SECONDS.sleep(duration);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        String ret = sender+": "+title;
        return ret;
    }

}

2. 创建一个名为ReportRequest的类,实现Runnable接口,这个类将模拟请求获取报告。

import java.util.concurrent.CompletionService;


public class ReportRequest implements Runnable {
    private String name;
    private CompletionService<String> service;
    
    public ReportRequest(String name, CompletionService<String> service){
        this.name = name;
        this.service = service;
    }
    
    @Override
    public void run() {
        ReportGenerator reportGenerator = new ReportGenerator(name, "Report");
        service.submit(reportGenerator);
    }

}

3. 创建名称为ReportProcessor的类,并实现Runnable接口。这个类将获得ReportGenerator的结果。

import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;


public class ReportProcessor implements Runnable {
    private CompletionService<String> service;
    private boolean end;
    
    public ReportProcessor(CompletionService<String> service){
        this.service = service;
        end = false;
    }
    
    @Override
    public void run() {
        try {
            while(!end){
                Future<String> result = service.poll(20, TimeUnit.SECONDS);
                if(result!=null){
                    String report = result.get();
                    System.out.printf("ReportReceiver: Report Received: %s\n", report);
                }
            }
            System.out.println("ReportSender: End");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void setEnd(boolean end) {
        this.end = end;
    }

}

4. 实现范例的主类Main,并实现main()方法。

import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        ExecutorService executor =  Executors.newCachedThreadPool();
        CompletionService<String> service = new ExecutorCompletionService<>(executor);
        ReportRequest faceRequest = new ReportRequest("Face", service);
        ReportRequest onlineRequest = new ReportRequest("Online", service);
        Thread faceThread = new Thread(faceRequest);
        Thread onlineThread = new Thread(onlineRequest);
        ReportProcessor processor = new ReportProcessor(service);
        Thread senderThread = new Thread(processor);
        //启动三个线程
        System.out.println("Main: Starting the Threads");
        faceThread.start();
        onlineThread.start();
        senderThread.start();
        System.out.println("Main: Waiting for the report generators.");
        try {
            faceThread.join();
            onlineThread.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Main: Shutting down the executor.");
        executor.shutdown();
        //等待所有任务执行结束
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //结束ReportProcessor的执行
        processor.setEnd(true);
        System.out.println("Main: Ends");
    }
}

5. 程序运行结果如下

Main: Starting the Threads
Main: Waiting for the report generators.
Main: Shutting down the executor.
Online_Report: ReportGenerator: Generating a report during 5 seconds
Face_Report: ReportGenerator: Generating a report during 6 seconds
ReportReceiver: Report Received: Online: Report
ReportReceiver: Report Received: Face: Report
ReportSender: End
Main: Ends

  CompletionService类可以执行Callable或Runnable类型的任务。在这个范例中,使用的是Callable类型的任务,但是,也可以发送Runnable对象给它。由于Runnable对象不能产生结果,因此CompletionService的基本原则不适用于此。

  ComletionService类提供了其他两种方法来获取任务已经完成的Future对象。这些方法如下:

  • poll():无参数的poll()方法用于检查队列中是否有Future对象。如果队列为空,则立即返回null。否则,它将返回队列中的第一个元素,并移除这个元素。
  • take():这个方法也没有参数,它检查队列中是否有Future对象。如果队列为空,它将阻塞线程直到队列中有可用的元素。如果队列中有元素,它将返回队列中的第一个元素,并且移除这个元素。

 11. 处理在执行器中被拒绝的任务

  当我们想结束执行器的执行时,调用shutdown()方法来表示执行器应结束。但是,执行器只有在等待正在运行的任务或者等待执行的任务结束后,才能真正结束。

  如果在shutdown()方法与执行器结束之间发送了一个任务给执行器,这个任务会被拒绝,因为这个时间段执行器已不再接受任务了。ThreadPoolExecutor类提供了一套机制,当任务被拒绝时调用这套机制来处理它们。

  下面我们将学习如何处理执行器中被拒绝的任务,这些任务实现了RejectedExecutionHandler接口。

1. 创建一个名为RejectedTaskContraller的类,并实现RejectedExecutionHandler接口,然后实现接口的rejectedExecutation()方法,在控制台输出已被拒绝的任务的名称和执行器状态。

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;


public class RejectedTaskController implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.printf("RejectedTaskController: The task %s has been rejected\n", r.toString());
        System.out.printf("RejectedTaskController: %s\n", executor.toString());
        System.out.printf("RejectedTaskController: Terminating: %s\n", executor.isTerminating());
        System.out.printf("RejectedTaskController: Terminated: %s\n", executor.isTerminated());
    }

}

2. 创建一个名为Task的类,并实现Runnable接口。

import java.util.concurrent.TimeUnit;

public class Task implements Runnable {
    private String name;
    
    public Task(String name){
        this.name = name;
    }
    
    @Override
    public void run() {
        System.out.println("Task "+name+": Starting");
        long duration = (long) (Math.random()*10);
        System.out.printf("Task %s: Processing a task during %d seconds\n", name, duration);
        try {
            TimeUnit.SECONDS.sleep(duration);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Task "+name+": Ending");
    }

    @Override
    public String toString() {
        return name;
    }
    
}

3. 创建范例的主类Main,并实现main()方法。

import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        //创建执行器对象
        ThreadPoolExecutor executor =  (ThreadPoolExecutor) Executors.newCachedThreadPool();
        //创建RejectedTaskController对象来管理被拒绝的任务
        RejectedTaskController controller = new RejectedTaskController();
        //设置任务呗拒绝时的处理程序
        executor.setRejectedExecutionHandler(controller);
        System.out.println("Main: Starting");
        for(int i=0;i<3;i++){
            Task task = new Task("Task"+i);
            executor.submit(task);
        }
        //调用shutdown()方法关闭执行器
        System.out.println("Main: Shutting down the Executor.");
        executor.shutdown();
        //创建另一个任务并发送给执行器
        System.out.println("Main: Sending another Task.");
        Task task = new Task("RejectedTask");
        executor.submit(task);
        System.out.println("Main: End");
    }
}

4. 程序运行结果如下

Main: Starting
Main: Shutting down the Executor.
Main: Sending another Task.
Task Task0: Starting
RejectedTaskController: The task java.util.concurrent.FutureTask@1a897a9 has been rejected
Task Task1: Starting
Task Task1: Processing a task during 0 seconds
Task Task1: Ending
Task Task2: Starting
Task Task2: Processing a task during 6 seconds
Task Task0: Processing a task during 1 seconds
RejectedTaskController: java.util.concurrent.ThreadPoolExecutor@1284fd4[Shutting down, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 1]
RejectedTaskController: Terminating: true
RejectedTaskController: Terminated: false
Main: End
Task Task0: Ending
Task Task2: Ending
View Code

 

posted @ 2015-10-25 16:06  ~风轻云淡~  阅读(539)  评论(0编辑  收藏  举报