声明:本文是《 Java 7 Concurrency Cookbook 》的第七章,作者: Javier Fernández González 译者:许巧辉
定制ThreadPoolExecutor类
执行者框架(Executor framework)是一种机制,允许你将线程的创建与执行分离。它是基于Executor和ExecutorService接口及其实现这两个接口的ThreadPoolExecutor类。它有一个内部的线程池和提供允许你提交两种任务给线程池执行的方法。这些任务是:
- Runnable接口,实现没有返回结果的任务
- Callable接口,实现返回结果的任务
在这两种情况下,你只有提交任务给执行者。这个执行者使用线程池中的线程或创建一个新的线程来执行这些任务。执行者同样决定任务被执行的时刻。
在这个指南中,你将学习如何覆盖ThreadPoolExecutor类的一些方法,计算你在执行者中执行的任务的执行时间,并且将关于执行者完成它的执行的统计信息写入到控制台。
准备工作
这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。
如何做…
按以下步骤来实现的这个例子:
1.创建MyExecutor类,并指定它继承ThreadPoolExecutor类。
1 | public class MyExecutor extends ThreadPoolExecutor { |
2.声明一个私有的、ConcurrentHashMap类型的属性,并参数化为String和Date类,名为startTimes。
1 | private ConcurrentHashMap<String, Date> startTimes; |
3.实现这个类的构造器,使用super关键字调用父类的构造器,并初始化startTime属性。
1 | public MyExecutor( int corePoolSize, int maximumPoolSize, |
2 | long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> |
4 | super (corePoolSize, maximumPoolSize, keepAliveTime, unit, |
6 | startTimes= new ConcurrentHashMap<>(); |
4.覆盖shutdown()方法。将关于已执行的任务,正在运行的任务和待处理的任务信息写入到控制台。然后,使用super关键字调用父类的shutdown()方法。
02 | public void shutdown() { |
03 | System.out.printf( "MyExecutor: Going to shutdown.\n" ); |
04 | System.out.printf("MyExecutor: Executed tasks: |
05 | %d\n",getCompletedTaskCount()); |
06 | System.out.printf("MyExecutor: Running tasks: |
07 | %d\n",getActiveCount()); |
08 | System.out.printf("MyExecutor: Pending tasks: |
09 | %d\n",getQueue().size()); |
5.覆盖shutdownNow()方法。将关于已执行的任务,正在运行的任务和待处理的任务信息写入到控制台。然后,使用super关键字调用父类的shutdownNow()方法。
02 | public List<Runnable> shutdownNow() { |
03 | System.out.printf("MyExecutor: Going to immediately |
05 | System.out.printf("MyExecutor: Executed tasks: |
06 | %d\n",getCompletedTaskCount()); |
07 | System.out.printf("MyExecutor: Running tasks: |
08 | %d\n",getActiveCount()); |
09 | System.out.printf("MyExecutor: Pending tasks: |
10 | %d\n",getQueue().size()); |
11 | return super .shutdownNow(); |
6.覆盖beforeExecute()方法。写入一条信息(将要执行任务的线程名和任务的哈希编码)到控制台。在HashMap中,使用这个任务的哈希编码作为key,存储开始日期。
2 | protected void beforeExecute(Thread t, Runnable r) { |
3 | System.out.printf("MyExecutor: A task is beginning: %s : |
4 | %s\n",t.getName(),r.hashCode()); |
5 | startTimes.put(String.valueOf(r.hashCode()), new Date()); |
7.覆盖afterExecute()方法。将任务的结果和计算任务的运行时间(将当前时间减去存储在HashMap中的任务的开始时间)的信息写入到控制台。
02 | protected void afterExecute(Runnable r, Throwable t) { |
03 | Future<?> result=(Future<?>)r; |
05 | System.out.printf( "*********************************\n" ); |
06 | System.out.printf( "MyExecutor: A task is finishing.\n" ); |
07 | System.out.printf( "MyExecutor: Result: %s\n" ,result.get()); |
08 | Date startDate=startTimes.remove(String.valueOf(r. |
10 | Date finishDate= new Date(); |
11 | long diff=finishDate.getTime()-startDate.getTime(); |
12 | System.out.printf( "MyExecutor: Duration: %d\n" ,diff); |
13 | System.out.printf( "*********************************\n" ); |
14 | } catch (InterruptedException | ExecutionException e) { |
8.创建一个SleepTwoSecondsTask类,它实现参数化为String类的Callable接口。实现call()方法。令当前线程睡眠2秒,返回转换为String类型的当前时间。
1 | public class SleepTwoSecondsTask implements Callable<String> { |
2 | public String call() throws Exception { |
3 | TimeUnit.SECONDS.sleep( 2 ); |
4 | return new Date().toString(); |
9.实现这个例子的主类,通过创建Main类,并实现main()方法。
2 | public static void main(String[] args) { |
10.创建一个MyExecutor对象,名为myExecutor。
1 | MyExecutor myExecutor= new MyExecutor( 2 , 4 , 1000 , TimeUnit. |
2 | MILLISECONDS, new LinkedBlockingDeque<Runnable>()); |
11.创建一个参数化为String类的Future对象的数列,用于存储你将提交给执行者的任务的结果对象。
1 | List<Future<String>> results= new ArrayList<>();¡; |
12.提交10个Task对象。
1 | for ( int i= 0 ; i< 10 ; i++) { |
2 | SleepTwoSecondsTask task= new SleepTwoSecondsTask(); |
3 | Future<String> result=myExecutor.submit(task); |
13.使用get()方法,获取前5个任务的执行结果。将这些信息写入到控制台。
1 | for ( int i= 0 ; i< 5 ; i++){ |
3 | String result=results.get(i).get(); |
4 | System.out.printf("Main: Result for Task %d : |
6 | } catch (InterruptedException | ExecutionException e) { |
14.使用shutdown()方法结束这个执行者的执行。
15.使用get()方法,获取后5个任务的执行结果。将这些信息写入到控制台。
1 | for ( int i= 5 ; i< 10 ; i++){ |
3 | String result=results.get(i).get(); |
4 | System.out.printf("Main: Result for Task %d : |
6 | } catch (InterruptedException | ExecutionException e) { |
16.使用awaitTermination()方法等待这个执行者的完成。
2 | myExecutor.awaitTermination( 1 , TimeUnit.DAYS); |
3 | } catch (InterruptedException e) { |
17.写入一条信息表明这个程序执行的结束。
1 | System.out.printf( "Main: End of the program.\n" ); |
它是如何工作的…
在这个指南中,我们已经通过继承ThreadPoolExecutor类和覆盖它的4个方法来实现我们自己定制的执行者。我们用beforeExecute()和afterExecute()方法来计算任务的执行时间。beforeExecute()方法是在任务执行之前被执行的。在这种情况下,我们使用HashMap来存储任务的开始(执行)时间。afterExecute()方法是在任务执行之后被执行的。你可以从HashMap中获取已完成任务的startTime(开始执行时间),然后,计算实际时间和那个时间(startTime)的差异来获取任务的执行时间。你也覆盖了shutdown()和shutdownNow()方法,将关于在执行者中已执行的任务的统计信息写入到控制台:
- 对于已执行的任务,使用getCompletedTaskCount()方法(获取)。
- 对于正在运行的任务,使用getActiveCount()方法(获取)。
对于待处理任务,使用执行者存储待处理任务的阻塞队列的size()方法(获取)。SleepTwoSecondsTask类,实现Callable接口,令它的执行线程睡眠2秒。Main类,使用它向你的执行者提交10个任务和演示其他类的特性。
执行这个程序,你将看到这个程序如何显示正在运行的每个任务的时间跨度,和根据调用shutdown()方法统计执行者。