Java8——串行流与并行流

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rubulai/article/details/89022315
并行流

 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

Fork/Join框架

 Fork/Join 框架:就是在必要的情况下,将一个大任务,拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
在这里插入图片描述
 传统多线程或线程池存在的问题:在我们将一个大的任务拆分成很多小任务并分配给多个线程的时候,某些任务在某些线程上是可能发生阻塞的,一旦线程发生阻塞之后分配到该线程上的其他任务也就会被阻塞在该线程中而不能顺利执行,而此时那些没有阻塞的线程在将任务执行完毕后会空闲下来,这样就会出现有些线程阻塞而有些线程却空闲的情况,导致CPU的资源并不能被充分利用(被阻塞的任务应该再次被分配到空闲的CPU线程上)。
 Fork/Join 框架与传统线程池的区别:Fork/Join 采用“工作窃取”模式(work-stealing),当线程在执行任务时它会将任务拆分成更小的任务,并将小任务加到该线程的任务队列中,这样每个线程都会有一个由很多小任务形成的队列。当一个线程把自己队列中的小任务执行完毕后(也就是说当一个线程在找不到可执行的小任务时),它并不是被动的等待程序给它分配新的任务,而是主动的从那些“忙碌的”线程中随机的挑选一个并从该忙碌线程的任务队列的末尾偷一个小任务并把它放在自己的任务队列中继续执行,直到每个线程都只有一个小任务在执行或者所有线程都空闲下来。相对于一般的线程池实现,fork/join 框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在 fork/join 框架实现中,如果某个子任务由于等待另外一个子任务的完成而无法继续运行。那么处理该子任务的线程会主动寻找其他尚未运行的子任务来执行,这种方式减少了线程的等待时间,提高了性能。
 Fork/Join框架适用于数据量很大的情况,因为任务的拆分等是需要时间的,如果数据量小的话就没有必要使用Fork/Join框架。
 Fork/Join框架在JDK1.7是就有了,但是使用起来相对繁琐,看一个示例:
  ①实现RecursiveTask接口,自己拆分任务

public class ForkJoinCalculate extends RecursiveTask<Long>{
	private static final long serialVersionUID = 13475679780L;
	private long start;
	private long end;
	private static final long THRESHOLD = 10000L; //临界值
	
	public ForkJoinCalculate(long start, long end) {
		this.start = start;
		this.end = end;
	}
	
	@Override
	protected Long compute() {
		long length = end - start;
		if(length <= THRESHOLD){
			long sum = 0;
			for (long i = start; i <= end; i++) {
				sum += i;
			}			
			return sum;
		}else{
			long middle = (start + end) / 2;			
			ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
			left.fork(); //拆分,并将该子任务压入线程队列			
			ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);
			right.fork();			
			return left.join() + right.join();
		}		
	}
}

  ②调用时:需要自己创建ForkJoinPool

public void test1(){
	long start = System.currentTimeMillis();
	
	ForkJoinPool pool = new ForkJoinPool();
	ForkJoinTask<Long> task = new ForkJoinCalculate(0L, 10000000000L);
	
	long sum = pool.invoke(task);
	System.out.println(sum);
	
	long end = System.currentTimeMillis();
	
	System.out.println("耗费的时间为: " + (end - start)); //112-1953-1988-2654-2647-20663-113808
}

 使用Java8的并行流:底层依然是Fork/Join框架,免去了我们自己编写Fork/Join相关的代码和自己做任务的拆分

Instant start = Instant.now();
LongStream.rangeClosed(0, 100000000000L)// 获取一个0-1千亿的元素的流
		.parallel()// 转为并行流
		.reduce(0, Long::sum);// 累加求和
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());

 使用Java8的并行流时默认会根据机器的CPU核心数开启线程,CPU有几个核心就会开几个线程,也可以自定义开启线程的数量(不推荐):

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");

猜你喜欢

转载自blog.csdn.net/rubulai/article/details/89022315