JAVA多线程——Fork-Join框架

Fork-Join框架

  • Java多线程发展
  • Fork-join概览
    • Fork-join中的重要类
  • 例子:ForkJoin框架的使用
  • 总结
    • ForkJoin 常用API 一览
      • 建立线程池
      • 建立ForkJoinTask任务(需重写compute方法)
      • ForkJoinTask提交子任务
      • ForkJoinTask等待子任务完成
      • 向线程池提交并运行任务
      • 获取并打印任务结果
      • 判断任务是否结束

Java多线程发展

Java多线程主要有以下三个实现路径:

  • Thread/Runnable/Thread组管理 (Java2提出)
  • Executors框架(Java5提出)
  • Fork-Join框架(Java7提出)

Fork-join概览

Fork-join是java7提出的一个框架,基于分解、治理、合并的方式来实现,适合与最小任务可确定,但是总任务量不好确定的场合。

Fork-join中的重要类

ForkJoinPool任务池:ExecutorService的一个实现类,是一种特殊的线程池。创建ForkJoinPool实例后,可以调用ForkJoinPoolsubmit(ForkJoinTask task)或者invoke(ForkJoinTask task)来执行指定任务。指定任务是指一个继承ForkJoinTask类的实例,ForkJoinTask的子类如下。
RecursiveAction:一个ForkJoinTask的抽象子类,一般不会返回值
RecursiveTask:一个供ForkJoinTask的抽象子类,一般会返回值,因此在继承时需指明其返回类型

例子:ForkJoin框架的使用

我们还是解决一个很大的数的和的计算问题,其方法采用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抽象类的ForkJoinTaskRecursiveTask顾名思义是一个可递归的任务类,事实上我们在例子中也能看到,当任务规模较大时,其做法是创建两个小的子类,将任务量一分为二分摊到子类里去,接下来使用invokeAll()函数来向线程池提交两个子任务,并等待两个子任务的结束subTask1.join()。注意RevursiveTask必须重写其compute方法,并加上protected字段,类似于 Threadrun()方法。
2)task是一个继承RecursiveTask抽象类的ForkJoinTask,它与Callable线程类都属于可返回值的线程类,并且都有一个专门的实例来接受结果(CallableFuture实例,RecursiveTask 则是用去父类ForkJoinTask的一个实例来获取 ), 不同的是, Callable线程类的 isDone()方法是属于Future实例的,而RecursiveTaskisDone()方法是自身拥有的。
3)ForkJoinTask得到的结果也用get()返回,但是需要加上toString()方法才能正确输出。
4)task将任务不断分摊为子任务,只有当子任务完成时其线程才会启动,之前会处于阻塞状态,所以当最后task.isDone()true时,判断整个线程池的所有线程已经全部执行完毕。

总结

ForkJoin 并行框架适用于一类特殊的计算任务,他们往往比较大,甚至总规模不好估计,但是子任务比较容易划分,

ForkJoin 常用API 一览

建立线程池

ForkJoinPool  pool = new ForkJoinPool();
// ForkJoinPool  pool = new ForkJoinPool(4); //可以定义大小

建立ForkJoinTask任务(需重写compute方法)

public class SumTask extends RecursiveTask<Long>
public class SumTask extends RecursiveAction
@Override
	protected Long compute()

ForkJoinTask提交子任务

invokeAll(subTask1, subTask2);

ForkJoinTask等待子任务完成

Long sum1 = subTask1.join();
Long sum2 = subTask2.join();

向线程池提交并运行任务

ForkJoinTask<Long> result = pool.submit(task);

获取并打印任务结果

System.out.println(result.get().toString());

判断任务是否结束

task.isDone()

你可能感兴趣的:(JAVA多线程)