创建fork/join池

Java 9并发编程指南 目录

本节中,学习如何使用fork/join框架基本元素,包括如下:

  • 创建ForkJoinPool对象执行任务
  • 创建在池中被执行的ForkJoinTask子类

在本范例中用到的fork/join框架主要特征如下所示:

  • 使用默认构造函数创建ForkJoinPool。

  • 在任务内部,使用Java API文档中退件的程序结构:

    if (problem size > default size){
        tasks=divide(task);
        execute(tasks);
    } else {
    	resolve problem using another algorithm;
    }
    
  • 用同步方式执行任务,当一个任务执行两个或多个子任务时,主任务等待子任务的结束。通过这种方式,执行任务的线程(称之为为工作线程)将寻找其它任务来运行,充分利用它们的执行时间。

  • 将要实现的任务不会返回任何结果,所以在实现过程中把RecursiveAction作为基类。

准备工作

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

实现过程

在本节中,将要实现一个更新产品列表价格的任务。初始任务将负责更新列表中的所有元素,这里使用长度10作为参照规模。如果任务更新超过10个元素,它将列表分成两个部分,并且创建两个子任务分别更新各自部分的产品价格。

通过如下步骤实现范例:

  1. 创建名为Product的类,用来存储产品名称和价格:

    public class Product {
    
  2. 定义名为name的私有String属性,名为price的私有double属性:

    	private String name;
    	private double price;
    
  3. 实现两个属性的getter和setter方法,由于实现非常简单,这里不展现源码:

  4. 创建名为ProductListGenerator的类,生成随机产品列:

    public class ProductListGenerator {
    
  5. 实现generate()方法,接收int参数作为列表长度,并且返回List ,作为生成的产品列表:

    	public List<Product> generate(int size) {
    
  6. 创建返回产品列表的对象:

    		List<Product> ret = new ArrayList<Product>();
    
  7. 生成产品列表,赋予所有产品相同价格,例如,10块,来检查程序是否工作正常:

    		for(int i = 0 ; i < size ; i ++) {
    			Product product = new Product();
    			product.setName("Product " + i);
    			product.setPrice(10);
    			ret.add(product);
    		}
    		
    		return ret;
    	}
    
  8. 创建名为Task的类,继承RecursiveAction类:

    public class Task extends RecursiveAction{
    
  9. 定义名为products的私有List 属性:

    	private List<Product> products;
    
  10. 定义名为first和last的两个私有int属性,用来确定任务需要处理的产品数量:

    	private int first;
    	private int last;
    
  11. 定义名为increment的私有double属性,存储产品价格的增量:

    	private double increment;
    
  12. 实现类构造函数,初始化类所有属性:

    	public Task(List<Product> products, int first, int last, double increment) {
    		this.products = products;
    		this.first = first;
    		this.last = last;
    		this.increment = increment;
    	}
    
  13. 实现compute()方法,用来实现任务的逻辑操作:

    	@Override
    	protected void compute() {	
    
  14. 如果last和first属性差小于10(任务只能更新小于10个产品的价格),使用updatePrices()方法增加这组产品的价格:

    		if( last - first < 10) {
    			updatePrices();
    
  15. 如果last和first属性差大于或等于10,创建两个新的Task对象,一个处理产品列表的前半部分,另一个处理第二部分,然后使用invokeAll()方法在ForkJoinPool中执行这两个任务:

    		}else{
    			int middle = (last + first) / 2;
    			System.out.printf("Task : Pending tasks : %s\n", getQueuedTaskCount());
    			Task t1 = new Task(products, first, middle + 1, increment);
    			Task t2 = new Task(products, middle + 1, last, increment);
    			invokeAll(t1, t2);
    		}
    	}
    
  16. 实现updatePrices()方法,这个方法更新产品列表中位置占据在first和last属性值之间的产品价格:

    	private void updatePrices() {
    		for(int i = first ; i < last ; i ++){
    			Product product = products.get(i);
    			product.setPrice(product.getPrice() * (1 + increment));
    		}
    	}
    
  17. 实现范例的主方法,创建一个包含main()方法的Main类:

    public class Main {
    	public static void main(String[] args) {
    
  18. 使用ProductListGenerator类创建数量为10000的产品列表:

    		ProductListGenerator generator = new ProductListGenerator();
    		List<Product> products = generator.generate(10000);
    
  19. 创建新的Task对象用来更新列表中所有产品的prices。参数first赋值0,参数last赋值10000(产品列表长度):

    		Task task = new Task(products, 0, products.size(), 0.2);
    
  20. 使用无参构造函数创建ForkJoinPool():

    		ForkJoinPool pool = new ForkJoinPool();
    
  21. 在线程池中使用execute()方法执行任务:

    		pool.execute(task);
    
  22. 实现一段代码,每隔五毫秒展示线程池中的变化并输出部分参数值到控制台,直到任务结束执行:

    		do {
    			System.out.printf("************************************\n");
    			System.out.printf("Main : Thread Count : %d\n", pool.getActiveThreadCount());
    			System.out.printf("Main : Thread Steal : %d\n", pool.getStealCount());
    			System.out.printf("Main : Parallelism : %d\n", pool.getParallelism());
    			System.out.printf("************************************\n");
    			try {
    				TimeUnit.MILLISECONDS.sleep(5);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}while (!task.isDone());
    
  23. 使用shutdown()方法关闭线程池:

    		pool.shutdown();
    
  24. 使用isCompletedNormally()方法检查任务是否已经完成且没有错误,这种情况下,输出信息到控制台:

    		if(task.isCompletedNormally()){
    			System.out.printf("Main : The process has completed normally.\n");
    		}
    
  25. 所有产品提高后的预期价格是12。输出价格不等于12的所有产品名称和价格,来检查所有的产品是否已经正确地提高价格:

    		for(int i = 0 ; i < products.size() ; i ++){
    			Product product = products.get(i);
    			if(product.getPrice() != 12) {
    				System.out.printf("Product %s : %f\n", product.getName(), product.getPrice());
    			}
    		}
    
  26. 输出指明程序结束的信息到控制台:

    		System.out.printf("Main : End of the program.\n");
    

#工作原理

在范例中,创建在池中执行的ForkJoinPool对象和ForkJoinTask类的子类。为了创建ForkJoinPool对象,用到无参构造函数,所以它将在默认配置下执行。创建的线程池线程数量等于计算机的处理核心数,当ForkJoinPool对象被创建时,这些线程也被创建,并且它们在池中等待直到一些任务进来执行。

因为Task类不返回结果,所以继承RecursiveAction类。在本节中用到推荐的程序结构来实现任务,如果任务需要更新超过10个产品,它将元素集拆分成两部分,创建两个任务,并且每个部分分配一个任务。在Task类中使用first和last属性了解产品列表中此任务需要更新价格的产品位置范围。只能在产品列表的一个副本中使用first和last属性,不能为每个任务创建不同的列表。

为了执行一个任务创建的子任务,这个任务调用invokeAll()方法。这是一个同步调用,在任务继续(也可能结束)执行之前,它等到所有子任务的结束。当任务等待其子任务时,执行任务的工作线程等待另一个任务并且执行。基于这种特性,fork/join框架在任务管理上比Runnable和Callable对象本身更高效。

ForkJoinTask类的invokeAll()方法是执行器与fork/join框架之间一个主要的不同点。在执行器框架中,这种情况下所有的任务都必须发送到执行器,这些任务包括在线程池中执行和控制任务的方法。范例中用到Task类中的invokeAll()方法,Task类继承RecursiveAction类,接着继承ForkJoinTask类。

使用execute()方法发送唯一任务到线程池来更新整个产品列表。这种情况属于异步调用,主线程继续执行。

范例中用到ForkJoinPool类的一些方法来检查在运行任务的状态和进展。ForkJoinPool类还提供跟多此类用途的方法,查看第九章“测试并发应用”中的“监控fork/join池”小节,包括这些方法的完整列表。

最后,与执行器框架相同,必须使用shutdown()方法结束ForkJoinPool。

下图显示本范例在控制台输出的部分执行信息:

pics/05_01.jpg

可以看到任务结束其工作,且产品价格已更新。

扩展学习

ForkJoinPool类还提供其它执行任务的方法,如下所示:

  • execute(Runnable task):这是本范例中execute()方法的另一种形式,这种情况下,发送Runnable任务到ForkJoinPool类。切记ForkJoinPool类不在Runnable对象上使用工作窃取算法,这个算法只用在ForkJoinTask对象上。
  • invoke(ForkJoinTask task):在本范例中学到当execute()方法进行异步调用时,invoke()方法对ForkJoinPool类进行同步调用。此方法调用直到作为参数传递的任务结束执行之后才返回。
  • 也可以使用ExecutorService接口中定义的invokeAll()和invokeAny()方法,这些方法接收Callable对象为参数。ForkJoinPool类无法使用Callable对象的工作窃取算法,所以最好使用ThreadPoolExecutor来执行这些对象。

ForkJoinTask类中还包括invokeAll()方法的其它形式,在本范例中也使用到,如下所示:

  • invokeAll(ForkJoinTask<?>… tasks) :此方法接受一个可变的参数列表,可以将尽可能多的ForkJoinTask对象作为参数传递。
  • invokeAll(Collection tasks) :此方法接受一个泛型T对象的集合(例如,ArrayList对象,LinkedList对象,或者TreeSet对象),这个泛型T必须是ForkJoinTask类或其子类。

虽然ForkJoinPool类设计成用来执行ForkJoinTask的对象,但也能直接执行Runnable和Callable对象。也可以使用ForkJoinTask类的adapt()方法来接受Callable对象或者Runnable对象,并且返回ForkJoinTask对象来执行这个任务。

更多关注

  • 第九章“测试并发应用”中的“监控fork/join池”小节。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值