java.util.concurrent包下的实现类:
java.util.concurrent.ForkJoinTask
java.util.concurrent.ForkJoinPool
ForkJoin特点:工作窃取
ForkJoin中维护的是双端队列,这样才可以工作窃取,当一个执行快的线程把本队列中的任务做完,去帮助别的线程执行任务,这时两个线程一个从队尾执行,一个从对头执行(类似于执行快的线程偷偷执行了执行慢的线程的任务)
ForkJoin使用方法:
1、创建一个自己计算任务类,继承ForkJoinTask,里面是一个任务ForkJoinTask
2、创建ForkJoinPool的实现类,通过它里面的execute(ForkJoinTask task)方法来执行ForkJoinTask
3,通过ForkJoinPool中的submit(ForkJoinTask<T> task)来提交ForkJoinTask任务并执行
注意:
ForkJoinPool是一个普通类,ForkJoinTask是抽象类
我们一般继承ForkJoinTask的子类(子类也是抽象类):
- 需要返回值继承:RecursiveTask
- 不需要返回值继承:RecursiveAction
用一个计算任务为例(难度依次增加):
- 1.普通方法计算
- 2.使用ForkJoin
public class ForkJoinTest{
public static void main(String[] args)throws Exception {
//test01();
test02();
//test3();
}
//普通方法计算
public static void test01(){
Long sum=0L;
//开始时间
long start=System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
//结束时间
long end = System.currentTimeMillis();
//计算使用时间
System.out.println("sum="+sum+" 时间:"+(end-start));
}
// 使用ForkJoin
public static void test02() throws ExecutionException, InterruptedException {
//开始时间
long start = System.currentTimeMillis();
//2、创建ForkJoinPool的实例,是用来执行ForkJoinTask
ForkJoinPool forkJoinPool = new ForkJoinPool();
//1、创建ForkJoinTask
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
//2、提交任务
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
//获得结果
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
public static void test3(){
long start = System.currentTimeMillis();
// Stream并行流
long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
}
//1、创建一个类继承ForkJoin的子类RecursiveTask,也是抽象类
class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp = 10000L;// 临界值
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
if ((end - start) < temp) {//小于临界值,使用普通方法
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else { // 大于临界值:ForkJoin 递归
long middle = (start + end) / 2; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); // 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
task2.fork(); // 拆分任务,把任务压入线程队列
return task1.join() + task2.join();//合并任务并返回
}
}
}
Future接口是Java标准API的一部分,在java.util.concurrent包中,该接口下的类可以用来异步执行
任务。
Future模式可以这样来描述:
Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。
Future接口是一个泛型接口,严格的格式应该是Future
,其中V代表了Future执行的任务返回值的类型。
boolean cancel (boolean mayInterruptIfRunning)
:取消任务的执行。参数指定是否立即中断任务执行,或者等任务结束boolean isCancelled ()
:任务是否已经取消,任务正常完成前将其取消,则返回 trueboolean isDone ()
:任务是否已经完成,需要注意的是如果任务正常终止、异常或取消,都将返回trueV get ()
throws InterruptedException, ExecutionException 等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationExceptionV get (long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一样,多了设置超时时间,参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException。//使用Executor创建一个线程
ExecutorService executor = Executors.newSingleThreadExecutor();
//创建一个FutureTask的实例
FutureTask<String> future =
new FutureTask<String>(new Callable<String>() {//使用Callable接口作为构造参数
public String call() {
//真正的任务在这里执行,这里的返回值类型为String,可以为任意类型
}});
//提交执行这个任务future,异步任务
executor.execute(future);
//在这里可以做别的任何事情
try {
result = future.get(5000, TimeUnit.MILLISECONDS); //取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果
} catch (InterruptedException e) {
futureTask.cancel(true);
} catch (ExecutionException e) {
futureTask.cancel(true);
} catch (TimeoutException e) {
futureTask.cancel(true);
} finally {
executor.shutdown();
}
Future接口实现类:都可以异步执行任务
- CompletableFuture
- ForkJoinTask
- FutureTask
- 通常使用FutureTask来处理我们的任务。
- FutureTask类同时又实现了Runnable接口,所以可以直接提交给Executor执行。
- 异步执行
- 成功回调
- 失败回调
CompletableFuture
是JDK1.8版本新引入的类,这个类实现了俩接口Future
和CompletionStage
:
- 使用CompletionStage接口,去支持完成时触发的函数和操作,能做一些ExecutorService配合Future做不了的工作
- ExecutorService配合Future需要等待isDone为true才能知道任务跑完了或者就是用get方法调用的时候会出现阻塞
- 而CompletableFuture就可以用then,when等等操作来防止以上的阻塞和轮询isDone的现象出现, 【也可以使用get方法调用的时候会出现阻塞】
一个CompletableFuture对象代表着一个任务:
- 创建CompletableFuture直接new对象也成
- complete意思就是这个任务完成了需要返回的结果
- 然后用get();方法可以获取到
public class Demo01 {
public static void main(String[] args) throws InterruptedException,ExecutionException {
runAsyncVoid();
supplyAsyncReturn();
}
public static void runAsyncVoid()throws InterruptedException,ExecutionException{
// 没有返回值的 runAsync 异步回调,【Async:异步】
CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
try {
//当线程沉睡时不阻塞,其他线程可执行
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
});
System.out.println("666666");
completableFuture.get();//阻塞获取结果,在获取结果之前后面的代码不执行
System.out.println("88888888");
}
public static void supplyAsyncReturn() throws InterruptedException,ExecutionException{
//有返回值的supplyAsync,【async:异步】
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int i = 10/0;
return 1024;
});
//System.out.println("22222222");
//completableFuture.get();//获取返回结果,不会阻塞,但是不处理错误,遇到错误会停止执行
//System.out.println("33333333");
//whenComplete中参数是 BiConsumer,有两个返回值,第一个是结果,第二个是错误信息
//因为BiConsumer是消费型接口,无法返回错误信息,要想返回错误信,需要使用exceptionally
//链式编程:返回值都一样,可以像链子一样,根据返回值再次调用
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); // 正常的返回结果
System.out.println("u=>" + u); // 错误信息:
//java.util.concurrent.CompletionException: java.lang.ArithmeticException:by zero
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233; // 可以获取到错误的返回结果
}).get());
}
}
666666
ForkJoinPool.commonPool-worker-9runAsync=>Void
88888888
ForkJoinPool.commonPool-worker-9supplyAsync=>Integer
t=>null
u=>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
233