Executor框架将线程创建与执行分开,此框架基于Executor和ExecutorService接口,以及实现这两个接口的ThreadPoolExecutor类。它包含一个内部线程池,并提供发送两种任务并在池线程中执行的方法。这些任务是:
- Runnable接口实现不返回值的任务
- Callable接口实现返回值的任务
这两种情况中,只将任务发送给执行器。executor使用其中一个池线程,或者创建一个新的线程来执行这些任务。执行器还决定任务执行的时刻。
本节将学习如何重写ThreadPoolExecutor类方法来计算在执行器中执行的任务运行时间,且当任务完成执行时,将执行器的统计信息输出到控制台。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为MyExecutor的类,继承ThreadPoolExecutor类:
public class MyExecutor extends ThreadPoolExecutor{
-
声明由字符串和日期类参数化的私有ConcurrentHashMap属性,名为startTimes:
private final ConcurrentHashMap<Runnable, Date> startTimes;
-
实现类构造函数,使用super关键字和初始化startTime属性调用父类构造函数:
public MyExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue); startTimes=new ConcurrentHashMap<>(); }
-
重写shutdown()方法,将执行、运行和挂起任务的信息输出到控制台。使用super关键字调用父类的shutdown()方法:
@Override public void shutdown() { System.out.printf("MyExecutor: Going to shutdown.\n"); System.out.printf("MyExecutor: Executed tasks: %d\n", getCompletedTaskCount()); System.out.printf("MyExecutor: Running tasks: %d\n", getActiveCount()); System.out.printf("MyExecutor: Pending tasks: %d\n", getQueue().size()); super.shutdown(); }
-
重写shutdownNow()方法,将执行、运行和挂起任务的信息输出到控制台。使用super关键字调用父类的shutdown()方法:
@Override public List<Runnable> shutdownNow() { System.out.printf("MyExecutor: Going to immediately shutdown.\n"); System.out.printf("MyExecutor: Executed tasks: %d\n", getCompletedTaskCount()); System.out.printf("MyExecutor: Running tasks: %d\n", getActiveCount()); System.out.printf("MyExecutor: Pending tasks: %d\n", getQueue().size()); return super.shutdownNow(); }
-
重写beforeExecute()方法,将准备执行任务的线程名称和任务的哈希码输出到控制台。使用任务哈希码作为键,将开始日期存储在HashMap中:
@Override protected void beforeExecute(Thread t, Runnable r) { System.out.printf("MyExecutor: A task is beginning: %s : %s\n", t.getName(),r.hashCode()); startTimes.put(r, new Date()); }
-
重写afterExecute()方法,将任务结果信息输出到控制台,通过减去当前日期的HashMap中存储的任务开始日期来计算任务的运行时间:
@Override protected void afterExecute(Runnable r, Throwable t) { Future<?> result=(Future<?>)r; try { System.out.printf("*********************************\n"); System.out.printf("MyExecutor: A task is finishing.\n"); System.out.printf("MyExecutor: Result: %s\n", result.get()); Date startDate=startTimes.remove(r); Date finishDate=new Date(); long diff=finishDate.getTime()-startDate.getTime(); System.out.printf("MyExecutor: Duration: %d\n",diff); System.out.printf("*********************************\n"); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
-
创建名为SleepTwoSecondsTask的类,实现String类参数化的Callable接口。实现call()方法,设置当前线程休眠2秒钟,返回转换成String类型的当前时间:
public class SleepTwoSecondsTask implements Callable<String>{ public String call() throws Exception { TimeUnit.SECONDS.sleep(2); return new Date().toString(); } }
-
实现本范例主类,创建名为Main的类,包含main()方法:
public class Main { public static void main(String[] args) {
-
创建名为myExecutor的MyExecutor对象:
MyExecutor myExecutor=new MyExecutor(4, 8, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());
-
创建String类参数化的Future对象列表,存储将要发送到执行器的任务的结果对象:
List<Future<String>> results=new ArrayList<>();
-
提交10个Task对象:
for (int i=0; i<10; i++) { SleepTwoSecondsTask task=new SleepTwoSecondsTask(); Future<String> result=myExecutor.submit(task); results.add(result); }
-
使用get()方法得到前五个任务的执行结果,输出到控制台:
for (int i=0; i<5; i++){ try { String result=results.get(i).get(); System.out.printf("Main: Result for Task %d : %s\n", i,result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }
-
使用shutdown()方法结束执行器运行:
myExecutor.shutdown();
-
使用get()方法得到后五个任务的执行结果,输出到控制台:
for (int i=5; i<10; i++){ try { String result=results.get(i).get(); System.out.printf("Main: Result for Task %d : %s\n", i,result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }
-
使用awaitTermination()方法等待执行器结束:
try { myExecutor.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }
-
将指明程序执行结束的信息输出到控制台:
System.out.printf("Main: End of the program.\n"); } }
工作原理
在本节中,通过继承ThreadPoolExecutor类和重写其四个方法实现了定制化执行器。beforeExecute()和afterExecute()方法用来计算任务执行时间。beforeExecute()方法在任务执行前运行,使用HashMap来存储任务的启动时间。afterExecute()方法在任务执行后运行,从HashMap中的到已经执行完的任务startTime,然后计算当前时间和startTime的差值得到任务的执行时间。还重写了shutdown()和shutdownNow()方法将执行器中任务执行的统计信息输出到控制台。这些任务包括:
- 执行的任务,使用getCompletedTaskCount()方法
- 当前运行的任务,使用getActiveCount()方法
- 待完成任务,使用阻塞队列的size()方法,执行器在其中存储挂起的任务
实现Callable接口的SleepTwoSecondsTask类将其执行线程休眠2秒,主类将10个任务发送给执行器,并使用执行器和其它类演示对应特性。
执行程序将看到其如何显示正在运行的每个任务的时间跨度,以及调用shutdown()方法时执行程序的统计信息。
更多关注
- 第四章“线程执行器”中的“创建线程执行器并控制其被拒任务”小节
- 本章“执行器对象中使用自定义ThreadFactory”小节