本节中,学习如何使用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个元素,它将列表分成两个部分,并且创建两个子任务分别更新各自部分的产品价格。
通过如下步骤实现范例:
-
创建名为Product的类,用来存储产品名称和价格:
public class Product {
-
定义名为name的私有String属性,名为price的私有double属性:
private String name; private double price;
-
实现两个属性的getter和setter方法,由于实现非常简单,这里不展现源码:
-
创建名为ProductListGenerator的类,生成随机产品列:
public class ProductListGenerator {
-
实现generate()方法,接收int参数作为列表长度,并且返回List ,作为生成的产品列表:
public List<Product> generate(int size) {
-
创建返回产品列表的对象:
List<Product> ret = new ArrayList<Product>();
-
生成产品列表,赋予所有产品相同价格,例如,10块,来检查程序是否工作正常:
for(int i = 0 ; i < size ; i ++) { Product product = new Product(); product.setName("Product " + i); product.setPrice(10); ret.add(product); } return ret; }
-
创建名为Task的类,继承RecursiveAction类:
public class Task extends RecursiveAction{
-
定义名为products的私有List 属性:
private List<Product> products;
-
定义名为first和last的两个私有int属性,用来确定任务需要处理的产品数量:
private int first; private int last;
-
定义名为increment的私有double属性,存储产品价格的增量:
private double increment;
-
实现类构造函数,初始化类所有属性:
public Task(List<Product> products, int first, int last, double increment) { this.products = products; this.first = first; this.last = last; this.increment = increment; }
-
实现compute()方法,用来实现任务的逻辑操作:
@Override protected void compute() {
-
如果last和first属性差小于10(任务只能更新小于10个产品的价格),使用updatePrices()方法增加这组产品的价格:
if( last - first < 10) { updatePrices();
-
如果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); } }
-
实现updatePrices()方法,这个方法更新产品列表中位置占据在first和last属性值之间的产品价格:
private void updatePrices() { for(int i = first ; i < last ; i ++){ Product product = products.get(i); product.setPrice(product.getPrice() * (1 + increment)); } }
-
实现范例的主方法,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
使用ProductListGenerator类创建数量为10000的产品列表:
ProductListGenerator generator = new ProductListGenerator(); List<Product> products = generator.generate(10000);
-
创建新的Task对象用来更新列表中所有产品的prices。参数first赋值0,参数last赋值10000(产品列表长度):
Task task = new Task(products, 0, products.size(), 0.2);
-
使用无参构造函数创建ForkJoinPool():
ForkJoinPool pool = new ForkJoinPool();
-
在线程池中使用execute()方法执行任务:
pool.execute(task);
-
实现一段代码,每隔五毫秒展示线程池中的变化并输出部分参数值到控制台,直到任务结束执行:
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());
-
使用shutdown()方法关闭线程池:
pool.shutdown();
-
使用isCompletedNormally()方法检查任务是否已经完成且没有错误,这种情况下,输出信息到控制台:
if(task.isCompletedNormally()){ System.out.printf("Main : The process has completed normally.\n"); }
-
所有产品提高后的预期价格是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()); } }
-
输出指明程序结束的信息到控制台:
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。
下图显示本范例在控制台输出的部分执行信息:
可以看到任务结束其工作,且产品价格已更新。
扩展学习
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池”小节。