1、Fork-Join框架

1.1 概述

在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可拆为止)。再将一个个小任务进行运算,最后将小任务运算的结果进行汇总(join)。

虽然在JDK 8之前,就已经有了Fork-Join框架,但是使用不是很方便。
示例代码如下:
//需求:计算0~n的累加和,并获取它的执行时间

//自定义ForkJoin任务类(该任务是一个递归任务),求start到end累加和
public class ForkJoinCalculate extends RecursiveTask<Long> {
	private static final long serialVersionUID = -4824658329518585605L;

	private long start;
	private long end;
	private static final long THRESHOLD = 10000;//临界值
	
	public ForkJoinCalculate(long start, long end) {
		this.start = start;
		this.end = end;
	}

	@Override
	protected Long compute() {
		long len = end - start;
		if(len <= THRESHOLD){
			long sum = 0;
			for(long i=start; i <= end; i++){
				sum += i;
			}
			return sum;
		}else{
			long mid = (start + end) / 2;
			ForkJoinCalculate left = new ForkJoinCalculate(start, mid);
			left.fork();//拆分子任务,同时压入线程队列
			ForkJoinCalculate right = new ForkJoinCalculate(mid+1, end);
			right.fork();
			return left.join() + right.join();//合并
		}
	}
}

JDK 8之前的Fork-Join框架,需要自定义Fork-Join任务类

@Test
public void test1(){
	Instant start = Instant.now();
	
	ForkJoinPool pool = new ForkJoinPool();
	ForkJoinTask<Long> task = new ForkJoinCalculate(0, 100000000000L);
	Long sum = pool.invoke(task);
	System.out.println(sum);
	
	Instant end = Instant.now();
	System.out.println("耗费时间为:"+Duration.between(start, end).toMillis());//47046
}

JDK1.8:优化了之前的ForkJoin,不必再自定义Fork-Join任务类

@Test
public void test2(){
	Instant start = Instant.now();
	
	LongStream.rangeClosed(0, 100000000000L)//返回从0到100000000000L元素范围 的Stream
	.parallel()								//将串行流转成并行流
	.reduce(0, Long::sum);
	
	Instant end = Instant.now();
	System.out.println("耗费时间为:"+Duration.between(start, end).toMillis());
}

tips:
将ForkJoin改为普通for循环

@Test
public void test3(){
	Instant start = Instant.now();
	
	long sum = 0L;
	for(long i=0; i<=100000000000L; i++){
		sum += i;
	}
	System.out.println(sum);
	
	Instant end = Instant.now();
	System.out.println("耗费时间为:"+Duration.between(start, end).toMillis());
}

发现如果n值不是很大的话,for循环的执行时间要小于ForkJoin的执行时间。所以只有当数据很大的时候,我们才会考虑使用ForkJoin。

1.2 Fork-Join框架与传统线程池的区别

  • Fork-Join框架采用“工作窃取”模式(work-stealing)。当执行新的任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中。如果当前线程所有子任务已全部执行完毕,它可以从一个随机线程的队列中偷取一个任务并把它放在自己的队列中。减少了线程的等待时间,提高了性能。
  • 而线程池中,如果一个线程正在执行的任务由于某些原因而无法执行(阻塞了),那么该线程会处于等待状态,其它线程无法执行该线程的任务,性能比较慢。

2、重复注解和类型注解

jdk 8对注解提供了2点改进,可重复注解和可用于类型的注解

//注解类
@Repeatable(MyAnnotations.class) //用于表示其注释声明的注释类型是可重复
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String value() default "abc";
}
//注解容器类:保证可以实现可重复注解
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
	MyAnnotation[] value();
}
//可重复注解
@MyAnnotation("hello")
@MyAnnotation("world")
public void show(){}

//可用于类型的注解
public void method(@MyAnnotation("java") String s){}

//通过反射获取注解的value
@Test
public void test() throws Exception{
	Class<AnnotationTest> c = AnnotationTest.class;
	Method show = c.getMethod("show");
	//获取show方法上定义的所有注解
	MyAnnotation[] mas = show.getAnnotationsByType(MyAnnotation.class);
	for(MyAnnotation ms:mas){
		System.out.println(ms.value());
	}
}