通常,当你在Java中开发一个简单的并发编程应用程序,你会创建一些Runnable对象并创建相应的Thread对象来运行它们。如果你开发一个运行多个并发任务的程序,这种途径的缺点如下:
你必须要实现很多相关代码来管理Thread对象(创建,结束,获得的结果)。
你必须给每个任务创建一个Thread对象。如果你执行一个大数据量的任务,那么这可能影响应用程序的吞吐量。
你必须有效地控制和管理计算机资源。如果你创建太多线程,会使系统饱和。
为了解决以上问题,从Java5开始JDK并发API提供一种机制。这个机制被称为Executor framework,接口核心是Executor,Executor的子接口是ExecutorService,而ThreadPoolExecutor类则实现了这两个接口。
这个机制将任务的创建与执行分离。使用执行者,你只要实现Runnable对象并将它们提交给执行者。执行者负责执行,实例化和运行这些线程。除了这些,它还可以使用线程池提高了性能。当你提交一个任务给这个执行者,它试图使用线程池中的线程来执行任务,从而避免继续创建线程。
Callable接口是Executor framework的另一个重要优点。它跟Runnable接口很相似,但它提供了两种改进,如下:
这个接口中主要的方法叫call(),可以返回结果。
当你提交Callable对象到执行者,你可以获取一个实现Future接口的对象,你可以用这个对象来控制Callable对象的状态和结果。
创建一个线程执行者:::::::
使用Executor framework的第一步就是创建一个ThreadPoolExecutor类的对象。你可以使用这个类提供的4个构造器或Executors工厂类来 创建ThreadPoolExecutor。一旦有执行者,你就可以提交Runnable或Callable对象给执行者来执行。
在这个指南中,你将会学习如何使用这两种操作来实现一个web服务器的示例,这个web服务器用来处理各种客户端请求。
1. public class Task implements Runnable{
2. private Date initDate;
3. private String name;
4. public Task(String name){
5. this.name = name;
6. initDate = new Date();
7. }
8. @Override
9. public void run() {
10. // TODO Auto-generated method stub
11. System.out.printf("%s: Task %s: Created on: %s\n",Thread.currentThread().getName(),name,initDate);
12. System.out.printf("%s: Task %s: Started on: %s\n",Thread.currentThread().getName(),name,new Date());
13. try {
14. Long duration=(long)(Math.random()*10);
15. System.out.printf("%s: Task %s: Doing a task during %dseconds\n",Thread.currentThread().getName(),name,duration);
16. TimeUnit.SECONDS.sleep(duration);
17. } catch (InterruptedException e) {
18. e.printStackTrace();
19. }
20. System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());
21. }
22. }
1. public class Server {
2. private ThreadPoolExecutor executor;
3. public Server(){
4. executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
5. }
6. public void executorTask(Task task){
7. System.out.println("server: a new task has arrived");
8. executor.execute(task);
9. System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
10. System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
11. System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
12. }
13. public void endServer(){
14. executor.shutdown();
15. }
16. }
1. public class Main {
2. public static void main(String[] args) {
3. Server server = new Server();
4. for (int i = 0; i < 100; i++) {
5. Task task = new Task("task"+i);
6. server.executorTask(task);
7. }
8. server.endServer();
9. }
10. }
Server类是这个示例的关键。它创建和使用ThreadPoolExecutor执行任务。
第一个重要点是在Server类的构造器中创建ThreadPoolExecutor。ThreadPoolExecutor有4个不同的构造器,但由于它 们的复杂性,Java并发API提供Executors类来构造执行者和其他相关对象。即使我们可以通过ThreadPoolExecutor类的任意一 个构造器来创建ThreadPoolExecutor,但这里推荐使用Executors类。
在本例中,你已经使用 newCachedThreadPool()方法创建一个缓存线程池。这个方法返回ExecutorService对象,所以它被转换为 ThreadPoolExecutor类型来访问它的所有方法。你已创建的缓存线程池,当需要执行新的任务会创建新的线程,如果它们已经完成运行任务,变成可用状态,会重新使用这些线程。线程重复利用的好处是,它减少线程创建的时间。缓存线程池的缺点是,为新任务不断创建线程, 所以如果你提交过多的任务给执行者,会使系统超载。
注意事项:使用通过newCachedThreadPool()方法创建的执行者,只有当你有一个合理的线程数或任务有一个很短的执行时间。
一旦你创建执行者,你可以使用execute()方法提交Runnable或Callable类型的任务。在本例中,你提交实现Runnable接口的Task类对象。
你也打印了一些关于执行者信息的日志信息。特别地,你可以使用了以下方法:
getPoolSize():此方法返回线程池实际的线程数。
getActiveCount():此方法返回在执行者中正在执行任务的线程数。
getCompletedTaskCount():此方法返回执行者完成的任务数。
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):此方法阻塞调用线程,直到执行者的任务结束或超时。TimeUnit类是个枚举类,有如下常 量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
注意事项:如果你想要等待任务的完成,不管它们的持续时间,请使用大的超时,如:DAYS。
创建一个大小固定的线程执行者:::::::::::::::
当你使用由Executors类的 newCachedThreadPool()方法创建的基本ThreadPoolExecutor,你会有执行者运行在某一时刻的线程数的问题。这个执行者为每个接收到的任务创建一个线程(如果池中没有空闲的线程),所以,如果你提交大量的任务,并且它们有很长的(执行)时间,你会使系统过载和引发应用程序性能不佳的问题。
如果你想要避免这个问题,Executors类提供一个方法来创建大小固定的线程执行者。这个执行者有最大线程数。 如果你提交超过这个最大线程数的任务,这个执行者将不会创建额外的线程,并且剩下的任务将会阻塞,直到执行者有空闲线程。这种行为,保证执行者不会引发应用程序性能不佳的问题。
1. public class Server {
2. private ThreadPoolExecutor executor;
3. public Server(){
4. executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
5. }
6. public void executorTask(Task task){
7. System.out.println("server: a new task has arrived");
8. executor.execute(task);
9. System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
10. System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
11. System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
12. System.out.printf("Server: Task Count: %d\n",executor.getTaskCount());
13. }
14. public void endServer(){
15. executor.shutdown();
16. }
17. }
你已经使用Executors类的newFixedThreadPool()方法来创建执行者。这个方法创建一个有最大线程数的执行者。如果你提交超过最大线程数的任务,剩下的任务将会被阻塞,直到有空闲的线程来处理它们。这个方法接收一个你想要让执行者拥有最大线程数的参数。在你的例子中,你已经创建了拥有5个线程的执行者。
写入的程序输出到控制台,你已经使用了ThreadPoolExecutor类的一些方法,包括:
getPoolSize():此方法返回线程池实际的线程数。
getActiveCount():此方法返回在执行者中正在执行任务的线程数。
你可以看出这些方法的输出是5,表明执行者有5个线程。它本没有超出既定的最大线程数。
当你提交最后的任务给执行者,它只有5个活动的线程。剩下的95个任务将等待空闲线程。我们使用getTaskCount()方法来显示有多少个任务已经提交给执行者。
不止这些…
Executors类同时提供newSingleThreadExecutor()方法。这是大小固定的线程执行者的一个极端例子。它创建只有一个线程的执行者,所以它在任意时刻只能执行一个任务。
执行者执行返因结果的任务:::::::::::::::::
Executor framework的一个优点是你可以并发执行返回结果的任务。Java并发API使用以下两种接口来实现:
Callable:此接口有一个call()方法。在这个方法中,你必须实现任务的(处理)逻辑。Callable接口是一个参数化的接口。意味着你必须表明call()方法返回的数据类型。
Future:此接口有一些方法来保证Callable对象结果的获取和管理它的状态。
在这个指南中,你将学习如何实现返回结果的任务,并在执行者中运行它们
1. package demo20;
2. import java.util.concurrent.Callable;
3. import java.util.concurrent.TimeUnit;
4. public class FactorialCalculator implements Callable<Integer> {
5. private Integer number;
6. public FactorialCalculator(Integer number) {
7. super();
8. this.number = number;
9. }
10. @Override
11. public Integer call() throws Exception {
12. // TODO Auto-generated method stub
13. int result = 1;
14. if (number ==1 || number == 0) {
15. result =1;
16. }else {
17. for (int i = 2; i < number; i++) {
18. result *= i;
19. }
20. TimeUnit.MILLISECONDS.sleep(20);
21. }
22. System.out.printf("%s: %d\n",Thread.currentThread().getName(),result);
23. return result;
24. }
25. }
1. package demo20;
2. import java.util.ArrayList;
3. import java.util.List;
4. import java.util.Random;
5. import java.util.concurrent.ExecutionException;
6. import java.util.concurrent.Executors;
7. import java.util.concurrent.Future;
8. import java.util.concurrent.ThreadPoolExecutor;
9. import java.util.concurrent.TimeUnit;
10. public class Main {
11. public static void main(String[] args) {
12. ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
13. List<Future<Integer>> list = new ArrayList<>();
14. Random random = new Random();
15. for (int i = 0; i < 10; i++) {
16. Integer number = random.nextInt(10);
17. FactorialCalculator calculator = new FactorialCalculator(number);
18. Future<Integer> result = executor.submit(calculator);
19. list.add(result);
20. }
21. do {
22. System.out.printf("Main: Number of Completed Tasks:%d\n",executor.getCompletedTaskCount());
23. for (int i=0; i<list.size(); i++) {
24. Future<Integer> result=list.get(i);
25. System.out.printf("Main: Task %d: %s\n",i,result.isDone());
26. }
27. try {
28. TimeUnit.MILLISECONDS.sleep(50);
29. } catch (Exception e) {
30. // TODO: handle exception
31. }
32. } while (executor.getCompletedTaskCount() < list.size());
33. System.out.printf("Main: Results\n");
34. for (int i=0; i<list.size(); i++) {
35. Future<Integer> result=list.get(i);
36. Integer number=null;
37. try {
38. number=result.get();
39. } catch (InterruptedException e) {
40. e.printStackTrace();
41. } catch (ExecutionException e) {
42. e.printStackTrace();
43. }
44. System.out.printf("Main: Task %d: %d\n",i,number);
45. executor.shutdown();
46. }
47. }
48. }
在这个指南中,你已经学习了如何使用Callable接口来启动返回结果的并发任务。你已经使用FactorialCalculator类实现了Callable接口,并参数化为Integer类型作为结果类型。因此,Integer就作为call()方法的返回类型。
Main类是这个示例的另一个关键点。它使用submit()方法提交一个Callable对象给执行者执行。这个方法接收Callable对象参数,并且返回一个Future对象,你可以以这两个目标来使用它:
你可以控制任务的状态:你可以取消任务,检查任务是否已经完成。基于这个目的,你已经使用isDone()方法来检查任务是否已经完成。
你 可以获取call()方法返回的结果。基于这个目的,你已经使用了get()方法。这个方法会等待,直到Callable对象完成call()方法的执 行,并且返回它的结果。如果线程在get()方法上等待结果时被中断,它将抛出InterruptedException异常。如果call()方法抛出 异常,这个方法会抛出ExecutionException异常。
当你调用Future对象的get()方法,并且这个对象控制的任务未完成,这个方法会阻塞直到任务完成。Future接口提供其他版本的get()方法:
get(long timeout, TimeUnit unit):这个版本的get方法,如果任务的结果不可用,等待它在指定的时间内。如果时间超时,并且结果不可用,这个方法返回null值。 TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
运行多个任务并处理第一个结果:::::::
在并发编程中的一个常见的问题就是,当有多种并发任务解决一个问题时,你只对这些任务的第一个结果感兴趣。比如,你想要排序一个数组。你有多种排序算法。 你可以全部启用它们,并且获取第一个结果(对于给定数组排序最快的算法的结果)。
在这个指南中,你将学习如何使用ThreadPoolExecutor类的场景。你将继续实现一个示例,一个用户可以被两种机制验证。如果使用其中一个机制验证通过,用户将被确认验证通过
1. public class UserValidate {
2. private String name;
3. public UserValidate(String name) {
4. super();
5. this.name = name;
6. }
7. public boolean validate(String name,String password){
8. Random random = new Random();
9. try {
10. long duration=(long)(Math.random()*10);
11. System.out.printf("Validator %s: Validating a user during %d seconds\n",this.name,duration);
12. TimeUnit.SECONDS.sleep(duration);
13. } catch (InterruptedException e) {
14. return false;
15. }
16. return random.nextBoolean();
17. }
18. public String getName() {
19. return name;
20. }
21. }
1. public class TaskValidator implements Callable<String> {
2. private UserValidate userValidate;
3. private String name;
4. private String password;
5. public TaskValidator(UserValidate userValidate, String name, String password) {
6. super();
7. this.userValidate = userValidate;
8. this.name = name;
9. this.password = password;
10. }
11. @Override
12. public String call() throws Exception {
13. if (!userValidate.validate(name, password)) {
14. System.out.printf("%s: The user has not been found\n",userValidate.getName());
15. throw new Exception("Error validating user");
16. }
17. System.out.printf("%s: The user has been found\n",userValidate.getName());
18. return userValidate.getName();
19. }
20. }
1. public class Main {
2. public static void main(String[] args) {
3. String userName = "test";
4. String password = "test";
5. UserValidate ldapValidator = new UserValidate("lpda");
6. UserValidate dbValidator = new UserValidate("database");
7. TaskValidator ldapTask=new TaskValidator(ldapValidator,userName, password);
8. TaskValidator dbTask=new TaskValidator(dbValidator,userName,password);
9. List<TaskValidator> list = new ArrayList<>();
10. list.add(dbTask);
11. list.add(ldapTask);
12. ExecutorService executor = Executors.newCachedThreadPool();
13. String result;
14. try {
15. result = executor.invokeAny(list);
16. System.out.printf("Main: Result: %s\n",result);
17. } catch (Exception e) {
18. // TODO: handle exception
19. }
20. executor.shutdown();
21. System.out.printf("Main: End of the Execution\n");
22. }
23. }
Main 类是这个示例的关键。ThreadPoolExecutor类中的invokeAny()方法接收任务数列,并启动它们,返回完成时没有抛出异常的第一个 任务的结果。该方法返回的数据类型与启动任务的call()方法返回的类型一样。在本例中,它返回String值。
这 个示例有两个返回随机Boolean值的UserValidator对象。每个UserValidator对象被一个实现TaskValidator类的Callable对象使用。如果UserValidator类的validate()方法返回false,TaskValidator类将抛出异常。否则,它将返回true值。
所以,我们有两个任务,可以返回true值或抛出异常。有以下4种情况:
两个任务都返回ture。invokeAny()方法的结果是第一个完成任务的名称。
第一个任务返回true,第二个任务抛出异常。invokeAny()方法的结果是第一个任务的名称。
第一个任务抛出异常,第二个任务返回true。invokeAny()方法的结果是第二个任务的名称。
两个任务都抛出异常。在本例中,invokeAny()方法抛出一个ExecutionException异常。
如果你多次运行这个示例,你可以获取以上这4种情况。
ThreadPoolExecutor类提供其他版本的invokeAny()方法:
invokeAny(Collection<? extends Callable<T>> tasks, long timeout,TimeUnit unit):此方法执行所有任务,并返回第一个完成(未超时)且没有抛出异常的任务的结果。TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS,MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
运行多个任务并处理所有结果::::::::
执行者框架允许你在不用担心线程创建和执行的情况下,并发的执行任务。它还提供了Future类,这个类可以用来控制任务的状态,也可以用来获得执行者执行任务的结果。
如果你想要等待一个任务完成,你可以使用以下两种方法:
如果任务执行完成,Future接口的isDone()方法将返回true。
ThreadPoolExecutor类的awaitTermination()方法使线程进入睡眠,直到每一个任务调用shutdown()方法之后完成执行。
这两种方法都有一些缺点。第一个方法,你只能控制一个任务的完成。第二个方法,你必须等待一个线程来关闭执行者,否则这个方法的调用立即返回。
ThreadPoolExecutor类提供一个方法,允许你提交任务列表给执行者,并且在这个列表上等待所有任务的完成。在这个指南中,你将学习如何使用这个特性,实现一个示例,执行3个任务,并且当它们完成时将结果打印出来。
1. public class Result {
2. private String name;
3. private int value;
4. public String getName() {
5. return name;
6. }
7. public void setName(String name) {
8. this.name = name;
9. }
10. public int getValue() {
11. return value;
12. }
13. public void setValue(int value) {
14. this.value = value;
15. }
16. }
1. public class Task implements Callable<Result>{
2. private String name;
3. public Task(String name){
4. this.name = name;
5. }
6. @Override
7. public Result call() throws Exception {
8. System.out.printf("%s: Staring\n",this.name);
9. try {
10. long duration=(long)(Math.random()*10);
11. System.out.printf("%s: Waiting %d seconds for results.\n",this.name,duration);
12. TimeUnit.SECONDS.sleep(duration);
13. } catch (InterruptedException e) {
14. e.printStackTrace();
15. }
16. int value = 0;
17. for (int i = 0; i < 5; i++) {
18. value += (int)(Math.random()*100);
19. }
20. Result result = new Result();
21. result.setName(name);
22. result.setValue(value);
23. System.out.println(this.name+": Ends");
24. return result;
25. }
26. }
1. public class Main {
2. public static void main(String[] args) {
3. ExecutorService executor = Executors.newCachedThreadPool();
4. List<Task> list = new ArrayList<>();
5. for (int i = 0; i < 3; i++) {
6. Task task = new Task(""+i);
7. list.add(task);
8. }
9. List<Future<Result>> resultList = null;
10. try {
11. resultList = executor.invokeAll(list);
12. } catch (Exception e) {
13. // TODO: handle exception
14. }
15. executor.shutdown();
16. System.out.println("Main: Printing the results");
17. for (int i = 0; i < resultList.size(); i++) {
18. Future<Result> future = resultList.get(i);
19. try {
20. Result result = future.get();
21. System.out.println(result.getName()+": "+result.getValue());
22. } catch (InterruptedException | ExecutionException e) {
23. // TODO Auto-generated catch block
24. e.printStackTrace();
25. }
26. }
27. }
28. }
在这个指南中,你已经学习了如何提交任务列表到执行者和使用invokeAll()方法等待它们的完成。这个方法接收Callable对象列表和返回 Future对象列表。这个列表将会有列表中每个任务的一个Future对象。Future对象列表的第一个对象是Callable对象列表控制的第一个任务,以此类推。
第一点要考虑的是,在存储结果对象的列表中声明的Future接口参数化的数据类型必须与使用的Callable对象的参数化相兼容。在本例中,你已经使用相同数据类型:Result类型。
另一个重要的一点就是关于invokeAll()方法,你会使用Future对象获取任务的结果。当所有的任务完成时,这个方法结束,如果你调用返回的Future对象的isDone()方法,所有调用都将返回true值。
执行者延迟运行一个任务:::::
执行者框架提供ThreadPoolExecutor类,使用池中的线程来执行Callable和Runnable任务,这样可以避免所有线程的创建操作。当你提交一个任务给执行者,会根据执行者的配置尽快执行它。在有些使用情况下,当你对尽快执行任务不感觉兴趣。你可能想要在一段时间之后执行任务或周期性地执行任务。基于这些目的,执行者框架提供 ScheduledThreadPoolExecutor类。
1. public class Task implements Callable<String> {
2. private String name;
3. public Task(String name) {
4. super();
5. this.name = name;
6. }
7. @Override
8. public String call() throws Exception {
9. // TODO Auto-generated method stub
10. System.out.printf("%s: Starting at : %s\n",name,new Date());
11. return "hello world";
12. }
13. }
1. public class Main {
2. public static void main(String[] args) throws Exception {
3. ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
4. System.out.printf("Main: Starting at: %s\n",new Date());
5. for (int i = 0; i < 5; i++) {
6. Task task = new Task("task"+i);
7. executor.schedule(task, i+1, TimeUnit.SECONDS);
8. }
9. executor.shutdown();
10. executor.awaitTermination(1, TimeUnit.DAYS);
11. System.out.printf("Main: Ends at: %s\n",new Date());
12. }
13. }
在这个示例中,关键的一点是Main类和ScheduledThreadPoolExecutor的管理。正如使用ThreadPoolExecutor类创建预定的执行者,Java建议利用Executors类。在本例中,你使用newScheduledThreadPool()方法。你用1作为参数传给这个方法。这个参数是你想要让线程池创建的线程数。
你必须使用schedule()方法,让执行者在一段时间后执行任务。这个方法接收3个参数,如下:
你想要执行的任务
你想要让任务在执行前等待多长时间
时间单位,指定为TimeUnit类的常数
在本例中,每个任务等待的秒数(TimeUnit.SECONDS)等于它在任务数组中的位置再加1。
注意事项:如果你想在给定时间执行一个任务,计算这个日期与当前日期的差异,使用这个差异作为任务的延迟。
你也可以使用Runnable接口实现任务,因为ScheduledThreadPoolExecutor类的schedule()方法接收这两种类型(Runnable和Callable)的任务。
尽管ScheduledThreadPoolExecutor类是ThreadPoolExecutor类的子类,因此,它继承 ThreadPoolExecutor类的所有功能,但Java建议使用ScheduledThreadPoolExecutor仅适用于调度任务。
最后,你可以配置ScheduledThreadPoolExecutor的行为,当你调用shutdown()方法时,并且有待处理的任务正在等待它们延迟结束。默认的行为是,不管执行者是否结束这些任务都将被执行。你可以使用ScheduledThreadPoolExecutor类的setExecuteExistingDelayedTasksAfterShutdownPolicy()方法来改变这种行为。使用false,调用 shutdown()时,待处理的任务不会被执行。
执行者周期性的运行一个任务::::::::::
执行者框架提供ThreadPoolExecutor类,使用池中的线程执行并发任务,从而避免所有线程的创建操作。当你提交任务给执行者,根据它的配置,它尽快地执行任务。当它结束,任务将被执行者删除,如果你想再次运行任务,你必须再次提交任务给执行者。
但是执行者框架通过ScheduledThreadPoolExecutor类可以执行周期性任务。在这个指南中,你将学习如何通过使用这个类的功能来安排一个周期性任务。
1. public class Main {
2. public static void main(String[] args) throws Exception {
3. ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
4. System.out.printf("Main: Starting at: %s\n",new Date());
5. Task task = new Task("task");
6. ScheduledFuture<?> future = executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
7. for (int i = 0; i < 10; i++) {
8. System.out.printf("Main: Delay: %d\n",future.getDelay(TimeUnit.MILLISECONDS));
9. try {
10. TimeUnit.SECONDS.sleep(500);
11. } catch (Exception e) {
12. // TODO: handle exception
13. }
14. }
15. executor.shutdown();
16. try {
17. TimeUnit.SECONDS.sleep(5);
18. } catch (Exception e) {
19. // TODO: handle exception
20. }
21. System.out.printf("Main: Finished at: %s\n",new Date());
22. }
23. }
当你想要使用执行者框架执行一个周期性任务,你需要ScheduledExecutorService对象。Java建议使用 Executors类创建执行者,Executors类是一个执行者对象工厂。在本例中,你应该使用newScheduledThreadPool()方法,创建一个 ScheduledExecutorService对象。这个方法接收池的线程数作为参数。正如在本例中你只有一个任务,你传入了值1作为参数。
一旦你有执行者需要执行一个周期性任务,你提交任务给该执行者。你已经使用了scheduledAtFixedRate()方法。此方法接收4个参数:你想要周期性执行的任务、第一次执行任务的延迟时间、两次执行之间的间隔期间、第2、3个参数的时间单位。它是TimeUnit类的常 量,TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
很重要的一点需要考虑的是两次执行之间的(间隔)期间,是这两个执行开始之间的一段时间。如果你有一个花5秒执行的周期性任务,而你给一段3秒时间,同一时刻,你将会有两个任务在执行。
scheduleAtFixedRate() 方法返回ScheduledFuture对象,它继承Future接口,这个方法和调度任务一起协同工作。ScheduledFuture是一个参数化接口(校对注:ScheduledFuture<V>)。在这个示例中,由于你的任务是非参数化的Runnable对象,你必须使用 问号作为参数。
你已经使用ScheduledFuture接口的一个方法。getDelay()方法返回直到任务的下次执行时间。这个方法接收一个TimeUnit常量,这是你想要接收结果的时间单位。
cheduledThreadPoolExecutor 提供其他方法来调度周期性任务。这就是scheduleWithFixedRate()方法。它与scheduledAtFixedRate()方法有一 样的参数,但它们之间的差异值得注意。在scheduledAtFixedRate()方法中,第3个参数决定两个执行开始的一段时间。在 scheduledWithFixedRate()方法中,参数决定任务执行结束与下次执行开始之间的一段时间。
当你使用 shutdown()方法时,你也可以通过参数配置一个SeduledThreadPoolExecutor的行为。shutdown()方法默认的行为是,当你调用这个方法时,计划任务就结束。 你可以使用ScheduledThreadPoolExecutor类的 setContinueExistingPeriodicTasksAfterShutdownPolicy()方法设置true值改变这个行为。在调用 shutdown()方法时,周期性任务将不会结束。
执行者取消一个任务:::::::
当你使用执行者工作时,你不得不管理线程。你只实现Runnable或 Callable任务和把它们提交给执行者。执行者负责创建线程,在线程池中管理它们,当它们不需要时,结束它们。有时候,你想要取消已经提交给执行者 的任务。在这种情况下,你可以使用Future的cancel()方法,它允许你做取消操作。在这个指南中,你将学习如何使用这个方法来取消已经提交给执行者的任务。
result.cancel(true);
你想要取消你已提交给执行者的任务,使用Future接口的cancel()方法。根据cancel()方法参数和任务的状态不同,这个方法的行为将不同:
如果这个任务已经完成或之前的已被取消或由于其他原因不能被取消,那么这个方法将会返回false并且这个任务不会被取消。
如果这个任务正在等待执行者获取执行它的线程,那么这个任务将被取消而且不会开始它的执行。如果这个任务已经正在运行,则视方法的参数情况而定。 cancel()方法接收一个Boolean值参数。如果参数为true并且任务正在运行,那么这个任务将被取消。如果参数为false并且任务正在运行,那么这个任务将不会被取消。
本示例的任务之所以能取消,确实与Thread.sleep(100)相关,因为Future.cancel()方法,其实是发送一个中断请求,而sleep能够响应中断,因此能达到取消正在执行任务的目的。也就是说只要执行中的任务能够响应中断,便能通过cancel()方法来取消任务的执行,所以不一定要用Thread.sleep(100),诸如对Thread.interrupted()的判断也行。至于jvm性能上的影响,由于没有实践测试过,不敢妄加下定论,但个人觉得,中断状态也应该只是对一个变量的判断,性能上应该不会有什么影响。如果不想以这种方式取消一个任务,也可以通过一个volatile变量来控制。
执行者控制一个任务完成::::
FutureTask类提供一个done()方法,允许你在执行者执行任务完成后执行一些代码。你可以用来做一些后处理操作,生成一个报告,通过e-mail发送结果,或释放一些资源。当执行的任务由FutureTask来控制完成,FutureTask会内部调用这个方法。这个方法在任务的结果设置和它的状态变成isDone状态之后被调用,不管任务是否已经被取消或正常完成。
默认情况下,这个方法是空的。你可以重写FutureTask类实现这个方法来改变这种行为。在这个指南中,你将学习如何重写这个方法,在任务完成之后执行代码。
1. public class ExecutableTask implements Callable<String>{
2. private String name;
3. public ExecutableTask(String name) {
4. super();
5. this.name = name;
6. }
7. public String getName(){
8. return name;
9. }
10. @Override
11. public String call() throws Exception {
12. try {
13. long duration = (long) (Math.random()*10);
14. System.out.printf("%s: Waiting %d seconds for results.\n",this.name,duration);
15. TimeUnit.SECONDS.sleep(duration);
16. } catch (Exception e) {
17. // TODO: handle exception
18. }
19. return "hello world i am " + name;
20. }
21. }
1. public class ResultTask extends FutureTask<String>{
2. private String name;
3. public ResultTask(Callable<String> callable) {
4. super(callable);
5. // TODO Auto-generated constructor stub
6. this.name = ((ExecutableTask)callable).getName();
7. }
8. @Override
9. protected void done() {
10. // TODO Auto-generated method stub
11. if (isCancelled()) {
12. System.out.println("has been canceled");
13. }
14. System.out.println("hasfinished o");
15. }
16. }
1. public class Main {
2. public static void main(String[] args) {
3. ExecutorService executor = Executors.newCachedThreadPool();
4. ResultTask resultTasks[] = new ResultTask[5];
5. for (int i = 0; i < resultTasks.length; i++) {
6. ExecutableTask executableTask = new ExecutableTask("task"+i);
7. resultTasks[i] = new ResultTask(executableTask);
8. executor.submit(resultTasks[i]);
9. }
10. try {
11. TimeUnit.SECONDS.sleep(5);
12. } catch (Exception e) {
13. // TODO: handle exception
14. }
15. for (int i = 0; i < resultTasks.length; i++) {
16. resultTasks[i].cancel(true);
17. }
18. executor.shutdown();
19. }
20. }
当控制任务执行完成后,FutureTask类调用done()方法。在这个示例中,你已经实现一个Callable对象,ExecutableTask类,然后一个FutureTask类的子类用来控制ExecutableTask对象的执行。
在建立返回值和改变任务的状态为isDone状态后,done()方法被FutureTask类内部调用。你不能改变任务的结果值和它的状态,但你可以关闭任务使用的资源,写日志消息,或发送通知。
执行者分离任务的启动和结果的处理:::::::::::::::
通常,当你使用执行者执行并发任务时,你将会提交 Runnable或Callable任务给这个执行者,并获取Future对象控制这个方法。你可以发现这种情况,你需要提交任务给执行者在一个对象中,而处理结果在另一个对象中。基于这种情况,Java并发API提供CompletionService类。
CompletionService 类有一个方法来提交任务给执行者和另一个方法来获取已完成执行的下个任务的Future对象。在内部实现中,它使用Executor对象执行任务。这种行为的优点是共享一个CompletionService对象,并提交任务给执行者,这样其他(对象)可以处理结果。其局限性是,第二个对象只能获取那些已经完成它们的执行的任务的Future对象,所以,这些Future对象只能获取任务的结果。
在这个指南中,你将学习如何使用CompletionService类把执行者启动任务和处理它们的结果分开。
1. public class ReportGenerator implements Callable<String> {
2. private String sender;
3. private String title;
4. public ReportGenerator(String sender, String title) {
5. super();
6. this.sender = sender;
7. this.title = title;
8. }
9. @Override
10. public String call() throws Exception {
11. try {
12. Long duration=(long)(Math.random()*10);
13. System.out.printf("%s_%s: ReportGenerator: Generating a report during %d seconds\n",this.sender,this.title,duration);
14. TimeUnit.SECONDS.sleep(duration);
15. } catch (InterruptedException e) {
16. e.printStackTrace();
17. }
18. String ret = sender +":"+title;
19. return ret;
20. }
21. }
1. public class ReportRequest implements Runnable{
2. private String name;
3. private CompletionService<String> service;
4. public ReportRequest(String name, CompletionService<String> service) {
5. super();
6. this.name = name;
7. this.service = service;
8. }
9. @Override
10. public void run() {
11. // TODO Auto-generated method stub
12. ReportGenerator reportGenerator = new ReportGenerator(name, "report");
13. service.submit(reportGenerator);
14. }
15. }
1. public class ReportProcesser implements Runnable{
2. private CompletionService<String> completionService;
3. private boolean end;
4. public ReportProcesser(CompletionService<String> completionService, boolean end) {
5. super();
6. this.completionService = completionService;
7. this.end = end;
8. }
9. @Override
10. public void run() {
11. // TODO Auto-generated method stub
12. while(!end){
13. try {
14. Future<String> result = completionService.poll(20, TimeUnit.SECONDS);
15. if (result != null) {
16. String report = result.get();
17. System.out.printf("ReportReceiver: Report Received:%s\n",report);
18. }
19. } catch (Exception e) {
20. // TODO: handle exception
21. }
22. }
23. System.out.printf("ReportSender: End\n");
24. }
25. public void setEnd(boolean end) {
26. this.end = end;
27. }
28. }
1. public class Main {
2. public static void main(String[] args) {
3. ExecutorService executor = Executors.newCachedThreadPool();
4. CompletionService<String> service = new ExecutorCompletionService<>(executor);
5. ReportRequest request = new ReportRequest("ss", service);
6. ReportRequest request22 = new ReportRequest("dd", service);
7. Thread requestThread = new Thread(request);
8. Thread requestThread2 = new Thread(request22);
9. ReportProcesser processer = new ReportProcesser(service, false);
10. Thread endThread = new Thread(processer);
11. System.out.println("starting the thread");
12. requestThread.start();
13. requestThread2.start();
14. endThread.start();
15. try {
16. System.out.printf("Main: Waiting for the repor generators.\n");
17. requestThread.join();
18. requestThread2.join();
19. } catch (InterruptedException e) {
20. e.printStackTrace();
21. }
22. System.out.printf("Main: Shutting down the executor.\n");
23. executor.shutdown();
24. try {
25. executor.awaitTermination(2, TimeUnit.DAYS);
26. } catch (InterruptedException e) {
27. // TODO Auto-generated catch block
28. e.printStackTrace();
29. }
30. processer.setEnd(true);
31. System.out.println("Main: Ends");
32. }
33. }
在示例的主类中,你使用Executors类的newCachedThreadPool()方法创建ThreadPoolExecutor。然后,使用这个对象初始化一个CompletionService对象,因为CompletionService需要使用一个执行者来执行任务。利用CompletionService执行一个任务,你需要使用submit()方法,如在ReportRequest类中。
当其中一个任务被执行,CompletionService完成这个任务的执行时,这个CompletionService在一个队列中存储Future对象来控制它的执行。poll()方法用来查看这个列队,如果有任何任务执行完成,那么返回列队的第一个元素,它是一个已完成任务的Future对象。当poll()方法返回一个Future对象时,它将这个Future对象从队列中删除。这种情况下,你可以传两个属性给那个方法,表明你想要等任务结果的时间,以防队列中的已完成任务的结果是空的。
一旦CompletionService对象被创建,你创建2个ReportRequest对象,用来执行3个ReportGenerator任务,每个都在CompletionService中,和一个ReportSender任务,它将会处理已提交给2个ReportRequest对象的任务所产生的结果。
CompletionService类可以执行Callable和Runnable任务。在这个示例中,你已经使用Callable,但你同样可以提交Runnable对象。由于Runnable对象不会产生结果,CompletionService类的理念不适用于这些情况。
这个类同样提供其他两个方法,用来获取已完成任务的Future对象。这两个方法如下:
poll():不带参数版本的poll()方法,检查是否有任何Future对象在队列中。如果列队是空的,它立即返回null。否则,它返回第一个元素,并从列队中删除它。
take():这个方法,不带参数。检查是否有任何Future对象在队列中。如果队列是空的,它阻塞线程直到队列有一个元素。当队列有元素,它返回第一元素,并从列队中删除它。
执行者控制被拒绝的任务::::::::::::::::
当你想要结束执行者的执行,你使用shutdown()方法来表明它的结束。执行者等待正在运行或等待它的执行的任务的结束,然后结束它们的执行。
如果你在shutdown()方法和执行者结束之间,提交任务给执行者,这个任务将被拒绝,因为执行者不再接收新的任务。ThreadPoolExecutor类提供一种机制,在调用shutdown()后,不接受新的任务
在这个指南中,你将学习如何通过实现RejectedExecutionHandler,在执行者中管理拒绝任务。
1. public class RejectedTaskController implements RejectedExecutionHandler {
2. @Override
3. public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
4. // TODO Auto-generated method stub
5. System.out.printf("RejectedTaskController: The task %s has been rejected\n",r.toString());
6. System.out.printf("RejectedTaskController: %s\n",executor.toString());
7. System.out.printf("RejectedTaskController: Terminating:%s\n",executor.isTerminating());
8. System.out.printf("RejectedTaksController: Terminated:%s\n",executor.isTerminated());
9. }
10. }
1. public class Task implements Runnable{
2. private String name;
3. public Task(String name) {
4. super();
5. this.name = name;
6. }
7. @Override
8. public void run() {
9. System.out.println("Task "+name+": Starting");
10. try {
11. long duration=(long)(Math.random()*10);
12. System.out.printf("Task %s: ReportGenerator: Generating a report during %d seconds\n",name,duration);
13. TimeUnit.SECONDS.sleep(duration);
14. } catch (InterruptedException e) {
15. e.printStackTrace();
16. }
17. System.out.printf("Task %s: Ending\n",name);
18. }
19. @Override
20. public String toString() {
21. // TODO Auto-generated method stub
22. return name;
23. }
24. }
1. public class Main {
2. public static void main(String[] args) {
3. RejectedTaskController controller = new RejectedTaskController();
4. ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
5. executor.setRejectedExecutionHandler(controller);
6. System.out.println("main:start");
7. for (int i = 0; i < 3; i++) {
8. Task task = new Task("task"+i);
9. executor.submit(task);
10. }
11. executor.shutdown();
12. Task task=new Task("RejectedTask");
13. executor.submit(task);
14. }
15. }
你可以看出当执行者关闭时,任务被拒绝提交。RejectecTaskController将有关于任务和执行者的信息写入到控制台。
为了管理执行者控制拒绝任务,你应该实现RejectedExecutionHandler接口。该接口有带有两个参数的方法rejectedExecution():
Runnable对象,存储被拒绝的任务
Executor对象,存储拒绝任务的执行者
每个被执行者拒绝的任务都会调用这个方法。你必须使用Executor类的setRejectedExecutionHandler()方法设置拒绝任务的处理器。
当执行者接收任务时,会检查shutdown()是否已经被调用了。如果被调用了,它拒绝这个任务。首先,它查找 setRejectedExecutionHandler()设置的处理器。如果有一个(处理器),它调用那个类的 rejectedExecution()方法,否则,它将抛RejectedExecutionExeption异常。这是一个运行时异常,所以你不需要 用catch语句来控制它。