定制ThreadPoolExecutor类

Java 9并发编程指南 目录

Executor框架将线程创建与执行分开,此框架基于Executor和ExecutorService接口,以及实现这两个接口的ThreadPoolExecutor类。它包含一个内部线程池,并提供发送两种任务并在池线程中执行的方法。这些任务是:

  • Runnable接口实现不返回值的任务
  • Callable接口实现返回值的任务

这两种情况中,只将任务发送给执行器。executor使用其中一个池线程,或者创建一个新的线程来执行这些任务。执行器还决定任务执行的时刻。

本节将学习如何重写ThreadPoolExecutor类方法来计算在执行器中执行的任务运行时间,且当任务完成执行时,将执行器的统计信息输出到控制台。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为MyExecutor的类,继承ThreadPoolExecutor类:

    public class MyExecutor extends ThreadPoolExecutor{
    
  2. 声明由字符串和日期类参数化的私有ConcurrentHashMap属性,名为startTimes:

    	private final ConcurrentHashMap<Runnable, Date> startTimes;
    
  3. 实现类构造函数,使用super关键字和初始化startTime属性调用父类构造函数:

    	public MyExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    		super(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue);
    		startTimes=new ConcurrentHashMap<>();
    	}
    
  4. 重写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();
    	}
    
  5. 重写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();
    	}
    
  6. 重写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());
    	}
    
  7. 重写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();
    		}
    	}
    }
    
  8. 创建名为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();
    	}
    }
    
  9. 实现本范例主类,创建名为Main的类,包含main()方法:

    public class Main {
    	public static void main(String[] args) {
    
  10. 创建名为myExecutor的MyExecutor对象:

    		MyExecutor myExecutor=new MyExecutor(4, 8, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());
    
  11. 创建String类参数化的Future对象列表,存储将要发送到执行器的任务的结果对象:

    		List<Future<String>> results=new ArrayList<>();
    
  12. 提交10个Task对象:

    		for (int i=0; i<10; i++) {
    			SleepTwoSecondsTask task=new SleepTwoSecondsTask();
    			Future<String> result=myExecutor.submit(task);
    			results.add(result);
    		}
    
  13. 使用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();
    			}
    		}
    
  14. 使用shutdown()方法结束执行器运行:

    		myExecutor.shutdown();
    
  15. 使用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();
    			}
    		}
    
  16. 使用awaitTermination()方法等待执行器结束:

    		try {
    			myExecutor.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  17. 将指明程序执行结束的信息输出到控制台:

    		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”小节
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值