Java多线程主要有以下三个实现路径:
Fork-join是java7提出的一个框架,基于分解、治理、合并的方式来实现,适合与最小任务可确定,但是总任务量不好确定的场合。
ForkJoinPool
任务池:ExecutorService
的一个实现类,是一种特殊的线程池。创建ForkJoinPool
实例后,可以调用ForkJoinPool
的submit(ForkJoinTask
或者invoke(ForkJoinTask
来执行指定任务。指定任务是指一个继承ForkJoinTask
类的实例,ForkJoinTask
的子类如下。
RecursiveAction
:一个ForkJoinTask的抽象子类,一般不会返回值
RecursiveTask
:一个供ForkJoinTask的抽象子类,一般会返回值,因此在继承时需指明其返回类型
我们还是解决一个很大的数的和的计算问题,其方法采用Fork-join的分治策略。
先来看主线程:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
public class SumTest {
public static void main(String[] args) throws ExecutionException, InterruptedException{
// 创建执行线程池
ForkJoinPool pool = new ForkJoinPool();
// ForkJoinPool pool = new ForkJoinPool(4); //可以定义大小
// 创建任务
SumTask task = new SumTask(1,10000000);
// 提交任务,注意这里使用ForkJoinTask实例来接收task的结果
ForkJoinTask<Long> result = pool.submit(task);
// 等待结果
do {
System.out.printf("Main: Thread Count: %d\n", pool.getActiveThreadCount());
System.out.printf("Main: Paralelism: %d\n", pool.getParallelism());// 返回线程池并行级别
try
{
Thread.sleep(50);
} catch (InterruptedException e)
{
e.printStackTrace();
}
} while (!task.isDone());
// 输出结果
System.out.println(result.get().toString());
}
}
在主线程中,利用ForkJoinPool pool = new ForkJoinPool();
来建立一个线程池。接下来该往线程池中放入任务task
,这个任务是一个递归可分割的任务,我们一会儿会看到。接下来我们利用do-while
循环,每50毫秒不断输出线程池的活跃线程数和并行级别(如不指定,默认为CPU个数)并检查是否所有任务都以执行完。
接下来我们来看一看SumTask
是如何运作的:
import java.util.concurrent.RecursiveTask;
// RecursiveTask可递归任务
public class SumTask extends RecursiveTask<Long>{
private int start;
private int end;
public SumTask(int start, int end)
{
this.start = start;
this.end = end;
}
public static final int threadhold = 5;
@Override
protected Long compute()
{
Long sum = 0L;
boolean canCompute = (end - start) <= threadhold;
if (canCompute)
{
// 任务足够小,直接进行执行
for (int i = start; i <= end; i++)
{
sum = sum + i;
}
} else
{
// 任务大于阈值,分裂为两个任务
int middle = (start + end) / 2;
SumTask subTask1 = new SumTask(start, middle);
SumTask subTask2 = new SumTask(middle + 1, end);
// 子任务的提交
invokeAll(subTask1, subTask2);
// 等待子任务的完成,当subTask1未完成时,join会一直阻塞
Long sum1 = subTask1.join();
Long sum2 = subTask2.join();
// 结果合并
sum = sum1 + sum2;
}
return sum;
}
}
在SumTask
中,任务根据其规模做出选择,大的会继续向下分的更小,而规模足够小的会直接进行运算。
这里有几点需要注意:
1)task
是一个继承RecursiveTask
抽象类的ForkJoinTask
,RecursiveTask
顾名思义是一个可递归的任务类,事实上我们在例子中也能看到,当任务规模较大时,其做法是创建两个小的子类,将任务量一分为二分摊到子类里去,接下来使用invokeAll()
函数来向线程池提交两个子任务,并等待两个子任务的结束subTask1.join()
。注意RevursiveTask
必须重写其compute
方法,并加上protected
字段,类似于 Thread
的run()
方法。
2)task
是一个继承RecursiveTask
抽象类的ForkJoinTask
,它与Callable
线程类都属于可返回值的线程类,并且都有一个专门的实例来接受结果(Callable
是 Future
实例,RecursiveTask
则是用去父类ForkJoinTask
的一个实例来获取 ), 不同的是, Callable
线程类的 isDone()
方法是属于Future
实例的,而RecursiveTask
的isDone()
方法是自身拥有的。
3)ForkJoinTask
得到的结果也用get()
返回,但是需要加上toString()
方法才能正确输出。
4)task
将任务不断分摊为子任务,只有当子任务完成时其线程才会启动,之前会处于阻塞状态,所以当最后task.isDone()
为true
时,判断整个线程池的所有线程已经全部执行完毕。
ForkJoin 并行框架适用于一类特殊的计算任务,他们往往比较大,甚至总规模不好估计,但是子任务比较容易划分,
ForkJoinPool pool = new ForkJoinPool();
// ForkJoinPool pool = new ForkJoinPool(4); //可以定义大小
public class SumTask extends RecursiveTask<Long>
public class SumTask extends RecursiveAction
@Override
protected Long compute()
invokeAll(subTask1, subTask2);
Long sum1 = subTask1.join();
Long sum2 = subTask2.join();
ForkJoinTask<Long> result = pool.submit(task);
System.out.println(result.get().toString());
task.isDone()