fork/join框架是Java9中最有趣的特性之一,它是Executor和ExecutorService接口的实现,能够直接执行Callable和Runnable任务,无需管理执行它们的线程。
此执行器目标是执行可以划分为更小部分的任务,其主要组成部分如下:
- 由ForkJoinTask类实现的特殊任务。
- 提供两个操作,用于将任务划分为子任务(fork操作)和等待这些子任务结束(join操作)。
- 它是一种命名为“工作窃取”的算法,优化线程池的使用。当任务等待其子任务时,正在执行此任务的线程会去运行其它任务。
fork/join框架的主类是ForkJoinPool类。在内部包括如下两个元素:
- 等待被执行的任务队列
- 执行任务的线程池
ForkJoinWorkerThread向Thread类添加新方法,比如创建线程时执行的onStart()方法和清理线程使用资源时调用的onTermination()方法。ForkJoinPool类使用ForkJoinWorkerThreadFactory接口的实现来创建其使用的工作线程。
本节将学习如何实现在ForkJoinPool类中使用的定制工作线程,以及在继承ForkJoinPool类和实现ForkJoinWorkerThreadFactory接口的工厂中如何使用此线程。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为MyWorkerThread的类,继承ForkJoinWorkerThread类:
public class MyWorkerThread extends ForkJoinWorkerThread{
-
声明和创建ThreadLocal属性,由名为taskCounter的Integer类参数化:
private final static ThreadLocal<Integer> taskCounter= new ThreadLocal<Integer>();
-
实现类构造函数:
protected MyWorkerThread(ForkJoinPool pool) { super(pool); }
-
重写onStart()方法,在其父类上调用此方法输出信息到控制条,且设置这个线程的taskCounter属性值为零:
@Override protected void onStart() { super.onStart(); System.out.printf("MyWorkerThread %d: Initializing task counter.\n", getId()); taskCounter.set(0); }
-
重写onTermination()方法,输出此线程的taskCounter属性值到控制台:
@Override protected void onTermination(Throwable exception) { System.out.printf("MyWorkerThread %d: %d\n", getId(),taskCounter.get()); super.onTermination(exception); }
-
实现addTask()方法,递增taskCounter属性值:
public void addTask(){ taskCounter.set(taskCounter.get() + 1);; } }
-
创建名为MyWorkerThreadFactory的类,实现ForkJoinWorkerThreadFactory接口。实现newThread()方法,创建和返回MyWorkerThread对象:
public class MyWorkerThreadFactory implements ForkJoinWorkerThreadFactory{ @Override public ForkJoinWorkerThread newThread(ForkJoinPool pool) { return new MyWorkerThread(pool); } }
-
创建名为MyRecursiveTask的类,继承Integer类参数化的RecursiveTask类:
public class MyRecursiveTask extends RecursiveTask<Integer> {
-
定义名为array的私有int数组:
private int array[];
-
定义名为start和end的两个私有int属性:
private int start, end;
-
实现类构造函数,初始化这些属性:
public MyRecursiveTask(int array[],int start, int end) { this.array=array; this.start=start; this.end=end; }
-
实现compute()方法,将数组中开始和结束位置之间所有元素相加。首先将正在执行任务的线程转换成MyWorkerThread对象,并且使用addTask()为此线程递增任务的计数:
@Override protected Integer compute() { Integer ret; MyWorkerThread thread=(MyWorkerThread)Thread.currentThread(); thread.addTask();
-
如果数组中开始和结束位置区间的元素数大于100,计算中间位置且创建两个新的MyRecursiveTask任务分别处理前后两部分。如果区间等于或小于100,计算开始和结束位置区间所有元素的和:
if (end-start>100) { int mid=(start+end)/2; MyRecursiveTask task1=new MyRecursiveTask(array,start,mid); MyRecursiveTask task2=new MyRecursiveTask(array,mid,end); invokeAll(task1,task2); ret=addResults(task1,task2); } else { int add=0; for (int i=start; i<end; i++) { add+=array[i]; } ret=add; }
-
设置线程休眠10毫秒,返回任务结果:
try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return ret; }
-
实现addResults()接口,计算并返回作为参数接收的两个任务的结果之和:
private Integer addResults(MyRecursiveTask task1, MyRecursiveTask task2) { int value; try { value = task1.get().intValue()+task2.get().intValue(); } catch (InterruptedException e) { e.printStackTrace(); value=0; } catch (Exception e) { e.printStackTrace(); value=0; } return value; } }
-
通过创建名为Main的类,添加main()方法,实现本范例主类:
public class Main { public static void main(String[] args) throws Exception{
-
创建名为factory的MyWorkerThreadFactory对象:
MyWorkerThreadFactory factory=new MyWorkerThreadFactory();
-
创建名为pool的ForkJoinPool对象,传递之前创建的factory对象到构造函数:
ForkJoinPool pool=new ForkJoinPool(4, factory, null, false);
-
创建100000个整数的数组,初始化所有元素为1:
int array[]=new int[100000]; for (int i=0; i<array.length; i++){ array[i]=1; }
-
创建新的task对象,计算数组中所有元素的和:
MyRecursiveTask task=new MyRecursiveTask(array,0,array.length);
-
使用execute()方法发送任务到线程池:
pool.execute(task);
-
使用join()方法等待任务结束:
task.join();
-
使用shutdown()方法关闭线程池:
pool.shutdown();
-
使用awaitTermination()方法等待执行器结束:
pool.awaitTermination(1, TimeUnit.DAYS);
-
使用get()方法,输出任务结果到控制台:
System.out.printf("Main: Result: %d\n",task.get());
-
输出指明程序结束的信息到控制台:
System.out.printf("Main: End of the program\n"); } }
工作原理
fork/join框架使用的线程称为工作线程。 Java提供ForkJoinWorkerThread类,此类继承了Thread类并实现fork/join框架使用的工作线程。
本节实现了继承ForkJoinWorkerThread类的MyWorkerThread类,并重写ForkJoinWorkerThread类的两个方法。范例目标是在每个工作线程中实现任务计数器,以便了解一个工作线程已经执行多少任务。通过ThreadLocal属性实现计数器,这样每个线程都将以透明的方式拥有自己的计数器。
重写ForkJoinWorkerThread类的onStart()方法初始化任务计数器,当工作线程开始执行时调用此方法。还重写了onTermination()方法输出任务计数器的值到控制台,当工作线程结束执行时调用此方法。此外,在MyWorkerThread类中实现了addTask()方法,用来递增每个线程的任务计数器。
ForkJoinPool类像Java并发API中所有执行器一样,使用工厂来创建线程。所以如果在ForkJoinPool类中使用MyWorkerThread线程,需要实现自定义的线程工厂。对于fork/join框架框架,这个工厂需要实现ForkJoinPool.ForkJoinWorkerThreadFactory类,此类只有一个方法来创建新的MyWorkerThread对象。
最后,只要使用已经创建的工厂初始化ForkJoinPool类,也就是在Main类中所做的,使用ForkJoinPool类构造函数。
下图显示本范例在控制台输出的部分执行信息:
可以看到ForkJoinPool对象如何执行四个工作线程,以及每个线程执行多少个任务。
扩展学习
需要注意当线程正常结束或者抛出异常时,才会调用ForkJoinWorkerThread类提供的onTermination()方法。此方法将Throwable对象作为参数接收,如果参数值为null,工作线程正常结束,但如果参数值不为空,线程抛出异常,所以需要编写必要的程序来处理这种情形。
更多关注
- 第五章“Fork/Join框架”中的“创建fork/join池”小节
- 第一章“线程管理”中的“工厂模式创建线程”小节