学习并发编程之初好像就一直对这个问题含混不清,在阅读《Java8实战》以及网络资源的时候对这个问题有了更进一步的认识,特此梳理一下java
这里引用Java8实战中的一张图片来加以说明编程
可能从上图简单来看,并发是单处理器核心多任务的交替执行,并行是多任务多处理器核心的同时执行,因为这个问题并无被盖棺定论规范化,致使可能不一样的人有不一样的理解,我也并不能给出一个严格意义上准确的定义,可是我综合他人的观点给出的本身的定义以下,并行是并发的一种表现形式,并发只强调两个任务的生命周期存在交集,即对用上面的任务1开始到结束的过程当中,若是任务2也开始了,那么咱们就认为任务1和任务2是并发的。可是今天想梳理的并非严格意义上的区分这两个关联紧密的概念,而是讨论这二者可以给咱们的程序带来什么?tomcat
并发更加侧重于压榨单个CPU的性能,以提升CPU利用率为目的,对于一串任务(task1,task2,task3...)高并发并不能加快这些任务整体完成的时间,甚至因为线程切换还会延长任务整体完成的时间,因此它并非以提升总体响应速率为目的的,而并行它使得多个任务(若是不相干,简化讨论,避免多核之间的一致性要求)能够在多个处理器核中获得真正的同时处理,而这个时候对于一系列的不相干任务来讲,利用并行计算,就能大大缩短总体的响应时间安全
答案是显然的,不能,并且因为线程切换带来的资源开销,单线程并发还会延长整个任务的处理时间?网络
有必要,并且很是有必要,首先咱们假定有四个任务1,2,3,4以下,每一个任务的执行耗时1个单位时间,若是按照单线程串行的执行方式,它应该是这样的多线程
对于task1来讲,它还能接收,毕竟执行1个单位时间它就拿到了它想要的结果,可是对于后面的task来讲就不满意了,特别是task4来讲,执行task4的耗时为1个单位时间,可是它须要等4个单位时间才能拿到结果,若是在多线程状况下,它是如何的呢?假设每一个task都另起了一个线程,且不考虑操做系统任务调度耗时等等,如今的处理状况是这样的并发
假如理想情况下,每一个任务被切割得足够小,那么最终每一个任务几乎是同时开始同时结束,那么每一个task的用时就是总耗时的平均值也就是2.5,这下task4总算开心了,它不用等那么久了。高并发
可是实际问题中,不可能把任务无限切分,操做系统的线程调度也是耗时操做,那么上面的结论就不必定那么可靠了,甚至可能每一个时间都超过3了,那还不如串行呢,至少task1和task2爽了,那为何还须要并发呢?性能
由于实际情况下,每一个任务的执行速度也不可能彻底相等,每一个任务执行的速度有快有慢,咱们如今假设task1执行用时须要1000个单位时间,若是在串行状况下,task1后面的全部任务都会被task1所拖累,须要等待的时间为1000加,而此时的并发执行策略中,虽然因为系统调度等等开销,task2,3,4仍然能够以一个与以前速度相差无几的时间响应,task1带来的恶劣影响也单单只影响到了本身。咱们上面的策略也就相似于tomcat对于请求的处理策略,针对每一个请求都另起一个线程processor来处理。学习
有必要,一般一个大任务是由多个小任务组合而成,若是按照CPU密集型和I/O密集型来划分任务类型的话,对于CPU密集型任务来讲,不管咱们再怎么多线程疯狂操做也好,在单核处理器中,最终都须要依靠处理器来作运算,多线程的开销无疑延长了整个任务的处理时间,可是在I/O密集型任务状况下(包括磁盘IO,网络IO),假设你发起了10次网络IO,发起了10个不一样的RPC调用,无疑多线程的方式可以让你同时发起多个请求,多个请求同时等待被调用方的响应以及网络延迟,不然你就只能按照串行的方式,每一个请求都须要等一个时延,而后再处理下一个请求,但其实这样的并发其实更加相似于并行,由于你发起的远程调用是另外一个处理器去帮你处理的,咱们所作的只不过是利用并发再一个请求傻等着的过程当中又发起了另外一个请求罢了
并行的好处是显而易见的,多个处理器干活确定是快于一我的干活的,对于上面讨论的状况,若是在多核心的处理器下,并发以后可能整个处理过程就是并行的,小的任务能够在多个处理器核心中同时运行,在这里也不太过多讨论并发安全的问题,主要讨论如何高效并行
在tomcat中想要并行很简单,你并发就好,若是你有多个处理器核心它天然会并行执行,可能并不太须要咱们对整个处理过程进行并行处理,关注更多的是不一样请求之间的并行,可是在一些场景下,可能就须要咱们关注整个任务自己的并行,这时候并行就不那么容易,假设你要计算1-1000000000的和,你固然能够选择并发执行,本身分割每一个处理器计算多少到多少的和,而后自行汇总结果,就想下面的代码同样
public class ConcurrentVsParallel { public static void main(String[] args) throws ExecutionException, InterruptedException { //串行 long sum=0; long time1=System.currentTimeMillis(); for (long i = 1; i <= 10000000000L; i++) { sum+=i; } System.out.println("串行计算结果为:"+sum); System.out.println("串行耗时:"+(System.currentTimeMillis()-time1)); long time2= System.currentTimeMillis(); long res = concurrentCal(10000000000L); System.out.println("计算结果为:"+res); System.out.println("并行耗时为:"+(System.currentTimeMillis()-time2)); } public static long concurrentCal(final long n) throws ExecutionException, InterruptedException { //4等分来处理 ExecutorService executor = Executors.newFixedThreadPool(4); long quarter=n/4; long allSum=0; Future[] parts = new Future[4]; for (int i = 0; i < 4L; i++) { final int temp=i; Future<Long> partSum = executor.submit(() -> { long sum = 0; for (long j = temp * quarter + 1; j <= (temp + 1) * quarter; j++) { sum += j; } return sum; }); parts[i]=partSum; } for (int i = 0; i < parts.length; i++) { allSum+=(long)parts[i].get(); } return allSum; } }
输出结果以下:
串行计算结果为:-5340232216128654848 串行耗时:4617 计算结果为:-5340232216128654848 并行耗时为:1847
上述的代码可以实现咱们既定的目标,可是存在着可读性和可拓展性的问题,性能也存在着问题,若是须要对(2-n)求和呢,很简单,给咱们的代码加入一个start便可,可是若是须要对(2-n)中全部的偶数求和呢?岂不是又须要改代码,更加严重的问题是任务规模的划分是定下来的,固然你也能够再添加一个参数设置任务规模的划分,可是上述这些操做都会致使代码的膨胀和难以维护,利用java8的Stream能够作以下简单实现
long time3=System.currentTimeMillis(); long res = LongStream.rangeClosed(1, 10000000000L).parallel().sum(); System.out.println("stream计算结果为:"+res); System.out.println("stream耗时为:"+(System.currentTimeMillis()-time3));
结果以下:
串行计算结果为:-5340232216128654848 串行耗时:4631 stream计算结果为:-5340232216128654848 stream耗时为:3605
虽然这里的耗时可能比不过咱们直接手动划分,并发的方式去进行计算,可是这里的代码可读性以及简洁读是很是好的,诚然这个结果也受限于我仅仅只有四核的垃圾笔记本,不管如何经过Stream的方式,java在并行方面的能力也是很是强的
《Java8实战》