ExecutorService接口继承了Executor接口,定义了一些生命周期的方法

  1. public interface ExecutorService extends Executor {
  2. void shutdown();
  3. List<Runnable> shutdownNow();
  4. boolean isShutdown();
  5. boolean isTerminated();
  6. boolean awaitTermination(long timeout, TimeUnit unit)
  7. throws InterruptedException;
  8. }

本文,我们逐一分析里面的每个方法。

ExecutorService生命周期-LMLPHP

ExecutorService生命周期-LMLPHP
ExecutorService生命周期-LMLPHP

首先,我们需要创建一个任务代码,这段任务代码主要是随机生成含有10个字符的字符串

  1. /**
  2. * 随机生成10个字符的字符串
  3. * @author dream-victor
  4. *
  5. */
  6. public class Task1 implements Callable<String> {
  7. @Override
  8. public String call() throws Exception {
  9. String base = "abcdefghijklmnopqrstuvwxyz0123456789";
  10. Random random = new Random();
  11. StringBuffer sb = new StringBuffer();
  12. for (int i = 0; i < 10; i++) {
  13. int number = random.nextInt(base.length());
  14. sb.append(base.charAt(number));
  15. }
  16. return sb.toString();
  17. }
  18. }

然后,我们还需要一个长任务,这里我们默认是沉睡10秒,

  1. /**
  2. * 长时间任务
  3. *
  4. * @author dream-victor
  5. *
  6. */
  7. public class LongTask implements Callable<String> {
  8. @Override
  9. public String call() throws Exception {
  10. TimeUnit.SECONDS.sleep(10);
  11. return "success";
  12. }
  13. }

OK,所有前期准备完毕,下面我们就来分析一下ExecutorService接口中和生命周期有关的这些方法:

1、shutdown方法:这个方法会平滑地关闭ExecutorService,当我们调用这个方法时,ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。这里我们先不举例在下面举例。

2、awaitTermination方法:这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。这个方法会使线程等待timeout时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。例如:

  1. ExecutorService service = Executors.newFixedThreadPool(4);
  2. service.submit(new Task1());
  3. service.submit(new Task1());
  4. service.submit(new LongTask());
  5. service.submit(new Task1());
  6. service.shutdown();
  7. while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
  8. System.out.println("线程池没有关闭");
  9. }
  10. System.out.println("线程池已经关闭");

这段代码中,我们在第三次提交了一个长任务,这个任务将执行10秒沉睡,紧跟着执行了一次shutdown()方法,假设:这时ExecutorService被立即关闭,下面调用service.awaitTermination(1, TimeUnit.SECONDS)方法时应该返回true,程序执行结果应该只会打印出:“线程池已经关闭”。但是,真实的运行结果如下:

  1. 线程池没有关闭
  2. 线程池没有关闭
  3. 线程池没有关闭
  4. 线程池没有关闭
  5. 线程池没有关闭
  6. 线程池没有关闭
  7. 线程池没有关闭
  8. 线程池没有关闭
  9. 线程池没有关闭
  10. 线程池已经关闭

这说明我们假设错误,service.awaitTermination(1, TimeUnit.SECONDS)每隔一秒监测一次ExecutorService的关闭情况,而长任务正好需要执行10秒,因此会在前9秒监测时ExecutorService为未关闭状态,而在第10秒时已经关闭,因此第10秒时输出:线程池已经关闭。这也验证了shutdown方法关闭ExecutorService的条件。

3、shutdownNow方法:这个方法会强制关闭ExecutorService,它将取消所有运行中的任务和在工作队列中等待的任务,这个方法返回一个List列表,列表中返回的是等待在工作队列中的任务。例如:

  1. ExecutorService service = Executors.newFixedThreadPool(3);
  2. service.submit(new LongTask());
  3. service.submit(new LongTask());
  4. service.submit(new LongTask());
  5. service.submit(new LongTask());
  6. service.submit(new LongTask());
  7. List<Runnable> runnables = service.shutdownNow();
  8. System.out.println(runnables.size());
  9. while (!service.awaitTermination(1, TimeUnit.MILLISECONDS)) {
  10. System.out.println("线程池没有关闭");
  11. }
  12. System.out.println("线程池已经关闭");

这段代码中,我们限制了线程池的长度是3,提交了5个任务,这样将有两个任务在工作队列中等待,当我们执行shutdownNow方法时,ExecutorService被立刻关闭,所以在service.awaitTermination(1, TimeUnit.MILLISECONDS)方法校验时返回的是false,因此没有输出:线程池没有关闭。而在调用shutdownNow方法时,我们接受到了一个List,这里包含的是在工作队列中等待执行的任务,由于线程池长度为3,且执行的都是长任务,所以当提交了三个任务后线程池已经满了,剩下的两次提交只能在工作队列中等待,因此我们看到runnables的大小为2,结果如下:

  1. 2
  2. 线程池已经关闭

4、isTerminated方法:这个方法会校验ExecutorService当前的状态是否为“TERMINATED”即关闭状态,当为“TERMINATED”时返回true否则返回false。

如果没有调用shutdown,即使所有任务都执行完了,那么isTerminated也返回false .线程池也会保持开着的例如:

  1. ExecutorService service = Executors.newFixedThreadPool(3);
  2. service.submit(new Task1());
  3. service.submit(new Task1());
  4. service.submit(new LongTask());
  5. service.shutdown();
  6. System.out.println(System.currentTimeMillis());
  7. while (!service.isTerminated()) {
  8. }
  9. System.out.println(System.currentTimeMillis());

这段代码我们执行了两个正常的任务和一个长任务,然后调用了shutdown方法,我们知道调用shutdown方法并不会立即关闭ExecutorService,这时我们记录一下监测循环执行前的时间,在没有关闭前我们一直进入一个空循环中,直到 ExecutorService关闭后退出循环,这里我们知道长任务执行时间大约为10秒,我们看一下上述程序运行结果:

  1. 1303298818621
  2. 1303298828634
  3. 相差:10013毫秒,转换一下除以1000,得到相差大约10秒

这10秒正好是长任务执行的时间,因此在 ExecutorService正常关闭后isTerminated方法返回true。

5、isShutdown方法:这个方法在ExecutorService关闭后返回true,否则返回false。如果没有调用了shutdown,即使所有任务执行完了那么也返回false方法,

线程池也会保持开着的,比较简单不再举例。

6.invokeAny(Collection<? extends Callable<T>> tasks)

有一类多线程编程模式是这样的:启动多个线程,相互独立的(无同步)去计算一个结果,当某一个线程得到结果之后,立刻终止所有线程,因为只需要一个结果就够了。

实际应用场景:作为男生,电脑上必须有苍老师的爱情动作片。这种片子必须藏得非常隐蔽,隐蔽到什么程度呢?自己都忘了把它藏哪里了,这可咋办啊?编写多线程程序,针对每一个硬盘分区,启动一个线程,搜索该硬盘分区上的所有文件,找名字中含有“苍老师”的文件。由于这些片子都是集中存放,因此,我只需要找到一个,就无需在继续寻找。例如,某线程在D盘找到了一个苍老师的文件,则此线程立刻终结,同时,处理C盘,E盘的线程也应该立刻终结,因为既然D盘找到了,其他盘搜索就毫无意义了,肯定没有结果。

ExecutorService.invokeAny()就是为此设计的,他接收的参数是一个List,List中的每一个元素必须实现Callable接口。他的功能是依此启动新的线程执行List中的任务,并将第一个得到的结果作为返回值,然后立刻终结所有的线程。其用法如下:

String s =  es.invokeAny(list)

7.invokeAll(Collection<? extends Callable<T>> tasks)

方法 invokeAll() 会调用存在于参数集合中的所有 Callable 对象,并且返回壹個包含 Future 对象的集合,你可以通过这個返回的集合来管理每個 Callable 的执行结果。
需要注意的是,任务有可能因为异常而导致运行结束,所以它可能并不是真的成功运行了。但是我们没有办法通过 Future 对象来了解到这個差异。
以下是壹個代码样例:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
}); List<Future<String>> futures = executorService.invokeAll(callables); for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
} executorService.shutdown();
8.invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
这里执行集合里面的所有的任务,timeout时间后返回<T> List<Future<T>>,这里返回的集合长度和执行的集合长度是一样的,只是,Future.isDone 都为true,而超出时间的任务将被取消isCancelled为true,如果iscancelled为true的 Future 直接调用get()方法,,,,,将会跑出cancellation异常. 9.invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
如果timeout后没有一个完成的任务,那么将抛出TimeoutException异常
InterruptedExceptionif interrupted while waiting
NullPointerExceptionif tasks, or unit, or any element task subject to execution is null
TimeoutExceptionif the given timeout elapses before any task successfully completes
ExecutionExceptionif no task successfully completes
RejectedExecutionExceptionif tasks cannot be scheduled for execution
 

以上讨论是基于ThreadPoolExecutor的实现,不同的实现会有所不同需注意。

04-15 12:52