志在指尖
用双手敲打未来

多线程编程学习

一、介绍
运用java8lambda表达式大半年了,一直都知道底层运用的是Fork/Join结构,今日总算有机会来学学Fork/Join结构了。
Fork/Join结构是Java7提供的一个用于并行履行使命的结构,是一个把大使命切割成若干个小使命,终究汇总每个小使命成果后得到大使命成果的结构。
Fork/Join的运行流程示意图:
比方,一个1+2+3+…+100的作业使命,咱们能够把它Fork成10个子使命,别离核算这10个子使命的运行成果。最终再把10个子使命的成果Join起来,汇总成最终的成果。
为了减少线程间的竞赛,一般把这些子使命别离放到不同的行列里,并为每个行列创立一个独自的线程来履行行列里的使命,线程和行列一一对应。可是,有的线程会先把自己行列里的使命干完,而其他线程对应的行列里还有使命等候处理。干完活的线程与其等着,不如去帮其它线程干活,所以它就去其他线程的行列里盗取一个使命来履行。而在这时它们会访问同一个行列,所以为了减少盗取使命线程和被盗取使命线程之间的竞赛,一般会运用双端行列,被盗取使命线程永远从双端行列的头部拿使命履行,而盗取使命的线程永远从双端行列的尾部拿使命履行。线程的这种履行办法,咱们称之为“作业盗取”算法。Java
二、规划
完成Fork/Join结构的规划,大略需求两步:
1.切割使命
首要咱们需求创立一个ForkJoin使命,把大使命切割成子使命,假如子使命不行小,则持续往下分,直到切割出的子使命满足小。
在Java中咱们能够运用ForkJoinTask类,它提供在使命中履行fork()和join()操作的机制,一般情况下,咱们只需求承继它的子类:
RecursiveAction—用于没有回来成果的使命
RecursiveTask—用于有回来成果的使命
2.使命履行并回来成果
切割的子使命别离放在双端行列里,然后启动几个线程别离从双端行列里获取使命履行。子使命履行完的成果都统一放在一个行列里,启动一个线程从行列里拿数据,然后兼并这些数据。
在Java中使命的履行需求经过ForkJoinPool来履行。
回到顶部
三、示例
来一个阿里面试题:百万级Integer数据量的一个array求和。
publicclassArrayCountTaskextendsRecursiveTask<Long>{/**
*阈值
*/privatestaticfinalIntegerTHRESHOLD=10000;privateInteger[]array;privateIntegerstart;privateIntegerend;publicArrayCountTask(Integer[]array,Integerstart,Integerend){this.array=array;this.start=start;this.end=end;
}@OverrideprotectedLongcompute(){longsum=0;//最小子使命核算if(end-start<=THRESHOLD){for(inti=start;i<end;i++){
sum+=array[i];
}
}else{//把大于阈值的使命持续往下拆分,有点相似递归的思维。recursive便是递归的意思。intmiddle=(start+end)>>>1;
ArrayCountTaskleftArrayCountTask=newArrayCountTask(array,start,middle);
ArrayCountTaskrightArrayCountTask=newArrayCountTask(array,middle,end);//履行子使命//leftArrayCountTask.fork();//rightArrayCountTask.fork();//invokeAll办法运用invokeAll(leftArrayCountTask,rightArrayCountTask);//等候子使命履行完,并得到其成果LongleftJoin=leftArrayCountTask.join();
LongrightJoin=rightArrayCountTask.join();//兼并子使命的成果sum=leftJoin+rightJoin;
}returnsum;
}
}
publicstaticvoidmain(String[]args){//1.造一个int类型的百万等级数组Integer[]array=newInteger[150000000];for(inti=0;i<array.length;i++){
array[i]=newRandom().nextInt(100);
}//2.普通办法核算成果longstart=System.currentTimeMillis();longsum=0;for(inti=0;i<array.length;i++){
sum+=array[i];
}longend=System.currentTimeMillis();
System.out.println(“普通办法核算成果:”+sum+”,耗时:”+(end-start));longstart2=System.currentTimeMillis();//3.fork/join结构办法核算成果ArrayCountTaskarrayCountTask=newArrayCountTask(array,0,array.length);
ForkJoinPoolforkJoinPool=newForkJoinPool();
sum=forkJoinPool.invoke(arrayCountTask);longend2=System.currentTimeMillis();
System.out.println(“fork/join结构办法核算成果:”+sum+”,耗时:”+(end2-start2));//结论://1.电脑i5-4300m,双核四线程//2.数组量少的时分,fork/join结构要进行线程创立/切换的操作,功能不明显。//3.数组量超过100000000,fork/join结构的功能才开始体现。}
ForkJoinTask与一般使命的主要差异在于它需求完成compute办法,在这个办法里,首要需求判断使命是否满足小,假如满足小就直接履行使命。假如不满足小,就必须切割成两个子使命,每个子使命在调用fork办法时,又会进入compute办法,看看当时子使命是否需求持续切割成子使命,假如不需求持续切割,则履行当时子使命并回来成果。运用join办法会等候子使命履行完并得到其成果。
在履行子使命时调用fork办法并不是最佳的选择,最佳的选择是invokeAll办法。由于履行compute()办法的线程本身也是一个worker线程,当对两个子使命调用fork()时,这个worker线程就会把使命分配给另外两个worker,可是它自己却停下来等候不干活了!这样就白白浪费了Fork/Join线程池中的一个worker线程,导致了4个子使命至少需求7个线程才干并发履行。
比方甲把400分成两个200后,fork()写法相当于甲把一个200分给乙,把另一个200分给丙,然后,甲成了监工,不干活,等乙和丙干完了他直接汇报作业。乙和丙在把200分拆成两个100的过程中,他俩又成了监工,这样,本来只需求4个工人的活,现在需求7个工人才干完成,其中有3个是不干活的。
ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组担任将寄存程序提交给ForkJoinPool的使命,而ForkJoinWorkerThread数组担任履行这些使命,ForkJoinWorkerThread体现的便是“作业盗取”算法。
当咱们调用ForkJoinTask的fork办法时,程序会调用ForkJoinWorkerThread的pushTask办法异步地履行这个使命,然后立即回来成果。
当咱们调用ForkJoinTask的join办法时,程序会堵塞当时线程并等候获取成果。
ForkJoinPool运用submit或invoke提交的差异:invoke同步履行,调用之后需求等候使命完成,才干履行后边的代码;submit是异步履行,只要在Future调用get的时分会堵塞。
ForkJoinPool承继自AbstractExecutorService,不是为了替代ExecutorService,而是它的补充,在某些使用场景下功能比ExecutorService更好。

未经允许不得转载:IT技术网站 » 多线程编程学习
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

志在指尖 用双手敲打未来

登录/注册IT技术大全

热门IT技术

C#基础入门   SQL server数据库   系统SEO学习教程   WordPress小技巧   WordPress插件   脚本与源码下载