Java并发和多线程2:3种方式实现数组求和
本篇演示3个数组求和的例子。
例子1:单线程
例子2:多线程,同步求和(如果没有计算完成,会阻塞)
例子3:多线程,异步求和(先累加已经完成的计算结果)
例子1-代码
package cn.fansunion.executorservice; public class BasicCaculator { public static long sum(int[] numbers){
long sum = 0;
for(int i=0;i<numbers.length;i++){
sum += numbers[i];
}
return sum;
}
}
例子2-代码
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
package cn.fansunion.executorservice; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
//并发计算数组的和,“同步”求和
public class ConcurrentCalculator { private ExecutorService exec;
//这个地方,纯粹是“一厢情愿”,“并行执行”不受咱们控制,取决于操作系统的“态度”
private int cpuCoreNumber;
private List<Future<Long>> tasks = new ArrayList<Future<Long>>(); class SumCalculator implements Callable<Long> {
private int[] numbers;
private int start;
private int end; public SumCalculator(final int[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
} public Long call() throws Exception {
Long sum = 0L;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
} public ConcurrentCalculator() {
cpuCoreNumber = Runtime.getRuntime().availableProcessors();
exec = Executors.newFixedThreadPool(cpuCoreNumber);
} public Long sum(final int[] numbers) {
// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
for (int i = 0; i < cpuCoreNumber; i++) {
int increment = numbers.length / cpuCoreNumber + 1;
int start = increment * i;
int end = increment * i + increment;
if (end > numbers.length)
end = numbers.length;
SumCalculator subCalc = new SumCalculator(numbers, start, end);
FutureTask<Long> task = new FutureTask<Long>(subCalc);
tasks.add(task);
if (!exec.isShutdown()) {
exec.submit(task);
}
}
return getResult();
} /**
* 迭代每个只任务,获得部分和,相加返回
*/
public Long getResult() {
Long result = 0l;
for (Future<Long> task : tasks) {
try {
// 如果计算未完成则阻塞
Long subSum = task.get();
result += subSum;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return result;
} public void close() {
exec.shutdown();
}
}
例子3-代码
在刚在的例子中,getResult()方法的实现过程中,迭代了FutureTask的数组,如果任务还没有完成则当前线程会阻塞。
如果我们希望任意字任务完成后就把其结果加到result中,而不用依次等待每个任务完成,可以使CompletionService。
生产者submit()执行的任务。使用者take()已完成的任务,并按照完成这些任务的顺序处理它们的结果 。也就是调用CompletionService的take方法是,会返回按完成顺序放回任务的结果。
CompletionService内部维护了一个阻塞队列BlockingQueue,如果没有任务完成,take()方法也会阻塞。
修改刚才的例子2,使用CompletionService:
package cn.fansunion.executorservice; import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; //并发计算数组的和,“异步”求和
public class ConcurrentCalculatorAsync { private ExecutorService exec;
private CompletionService<Long> completionService;
//这个地方,纯粹是“一厢情愿”,“并行执行”不受咱们控制,取决于操作系统的“态度”
private int cpuCoreNumber; class SumCalculator implements Callable<Long> {
private int[] numbers;
private int start;
private int end; public SumCalculator(final int[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
} public Long call() throws Exception {
Long sum = 0l;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
} public ConcurrentCalculatorAsync() {
cpuCoreNumber = Runtime.getRuntime().availableProcessors();
exec = Executors.newFixedThreadPool(cpuCoreNumber);
completionService = new ExecutorCompletionService<Long>(exec);
} public Long sum(final int[] numbers) {
// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
for (int i = 0; i < cpuCoreNumber; i++) {
int increment = numbers.length / cpuCoreNumber + 1;
int start = increment * i;
int end = increment * i + increment;
if (end > numbers.length){
end = numbers.length;
}
SumCalculator subCalc = new SumCalculator(numbers, start, end);
if (!exec.isShutdown()) {
completionService.submit(subCalc);
} }
return getResult();
} /**
* 迭代每个只任务,获得部分和,相加返回
*/
public Long getResult() {
Long result = 0l;
for (int i = 0; i < cpuCoreNumber; i++) {
try {
Long subSum = completionService.take().get();
result += subSum;
System.out.println("subSum="+subSum+",result="+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return result;
} public void close() {
exec.shutdown();
}
}
运行代码
package cn.fansunion.executorservice; import java.math.BigDecimal;
//数组求和3个Demo
public class ArraySumDemo {
public static void main(String[] args) {
int n = 200000000;
int[] numbers = new int[n];
for(int i=1;i<=n;i++){
numbers[i-1]=i;
}
basic(numbers); long time = System.currentTimeMillis();
concurrentCaculatorAsync(numbers);
long endTime=System.currentTimeMillis();
System.out.println("多核并行计算,异步相加:"+time(time,endTime)); long time2 = System.currentTimeMillis();
concurrentCaculator(numbers);
long endTime2=System.currentTimeMillis();
System.out.println("多核并行计算,同步相加:"+time(time2,endTime2));
} private static void basic(int[] numbers) {
long time1 = System.currentTimeMillis();
long sum=BasicCaculator.sum(numbers);
long endTime1 = System.currentTimeMillis();
System.out.println("单线程:"+time(time1,endTime1));
System.out.println("Sum:"+sum);
} private static double time(long time, long endTime) {
long costTime = endTime-time;
BigDecimal bd = new BigDecimal(costTime);
//本来想着,把毫秒转换成秒的,最后发现计算太快了
BigDecimal unit = new BigDecimal(1L);
BigDecimal s= bd.divide(unit,3);
return s.doubleValue();
} //并行计算,“同步”求和
private static void concurrentCaculator(int[] numbers) {
ConcurrentCalculator calc = new ConcurrentCalculator();
Long sum = calc.sum(numbers);
System.out.println(sum);
calc.close();
} //并行计算,“异步”求和
private static void concurrentCaculatorAsync(int[] numbers) {
ConcurrentCalculatorAsync calc = new ConcurrentCalculatorAsync();
Long sum = calc.sum(numbers);
System.out.println("Sum:"+sum);
calc.close();
}
}
控制台输出
单线程:93.0
Sum:20000000100000000
subSum=3750000175000002,result=3750000175000002
subSum=1250000075000001,result=5000000250000003
subSum=6250000275000003,result=11250000525000006
subSum=8749999574999994,result=20000000100000000
Sum:20000000100000000
多核并行计算,异步相加:786.0
20000000100000000
多核并行计算,同步相加:650.0
个人看法:3段代码的时间仅供参考,没有排除干扰因素。
总的来说,单线程执行更快一些,应该是由于“数组求和”本身,并不需要其它额外资源,不会阻塞。
而多线程,反而增加了“线程调度”的时间开销。
还可以看出,CPU计算还是非常快的。“200000000”2亿个整数相加,用了不到0.1秒的时间。
插曲
最开始看代码的时候,误解了。以为“根据CPU核心个数拆分任务”,这个时候的“多线程”就是“并行”了。
实际上,不一定,除了要看CPU的核数,还要看操作系统的分配。
// 根据CPU核心个数拆分任务,创建FutureTask并提交到Executor
for (int i = 0; i < cpuCoreNumber; i++) {
}
最开始,我还在考虑“单线程”、“多核并行+多线程并发”、“单核+多线程并发”,等好几种情况来实现“数组求和”。
最后,感觉自己还是想多了。“并行”应该不受自己控制,只能控制是“单线程”或者“多线程”。
“java并发编程-Executor框架”这篇文章中的“例子:并行计算数组的和。” 这句话,误导了我,根本不能保证是“并行计算”。
友情提示:网络上的文章,仅供参考学习,需要自己的判断。
关于Java-多核-并行-多线程,我初步认为“多线程可以并行执行,但不受我们自己的控制,取决于操作系统”。
网友的一些看法:
看法1:
java线程可以在运行在多个cpu核上吗?
我是一直都以为这个问题的答案是肯定的,也就是说可以运行在多核上。
但是有一天见到这样的一个理论,我就顿时毁三观了。
JVM在操作系统中是作为一个进程的,java所有的线程都运行自这个JVM进程中,
所以说java线程某个时间只可能运行在一个核上。
这个说法对我的打击太大了,我不能接受。于是就开始多方求证。网上搜索 和朋友一起讨论,
最终证实了java线程是可以运行在多核上的,为什么呢?
下面一句话将惊醒梦中人:
现代os都将线程作为最小调度单位,进程作为资源分配的最小单位。 在windows中进程是不活动的,
只是作为线程的容器。
也就是说,java中的所有线程确实在JVM进程中,但是CPU调度的是进程中的线程。
看法2:
JAVA中的多线程能在多CPU机器上并行执行吗?注意,我说的不是并发执行哦 。
我们用java写一个多线程程序,就启动了一个JVM进程,所以这些线程都是在这一个JVM进程之中的,我不知道同一时刻,能不能有多个CPU运行同一进程,进而并行执行这同一进程中的不同线程?一直很疑惑
你的思路是对的,CPU就是为了迎合操作系统的多线程从而提高系统的计算效率.但是具体分配任务到各个内核中去执行的并非JAVA与JVM而是操作系统.
也就是说,你所执行的多线程,可能会被分配到同一个CPU内核中运行.也可能非配到不同的cpu中运行.如果可以控制CPU的分配,那也应该是操作系统的api才能实现的了。
我用JAVA创建了一个线程,这时候有主线程和子线程都在运行,那意思双核CPU有可能在同一时刻点并行运行这两个线程咯?
我翻了好多JAVA的有关多线程的章节,似乎都没有说道多核CPU运行JAVA多线程,貌似都是已单核为例讲解的,所以我一直觉得可能都是并发的而不是并行的?
不是,你要将你的软件线程和计算机的CPU处理线程区分开呀.简单说,你是无法控制CPU对于任务的分配的.
更多代码示例:
http://git.oschina.net/fansunion/Concurrent(逐步更新中)
参考资料:
java并发编程-Executor框架
http://www.iteye.com/topic/366591
java线程可以在运行在多个cpu核上吗?
http://blog.csdn.net/maosijunzi/article/details/42527553
JAVA中的多线程能在多CPU上并行执行吗?注意,我说的不是并发执行哦
http://zhidao.baidu.com/link?url=e11sEOSNFoLTfVyP-5FfpktIXEgbMQkbLAzvgh8mn4V16n_qQas89voj5gVhOEkho0jRA7fp_vbnElxKgeQCDrOxGkcu6xAWaUniqpcWg33
Java并发和多线程2:3种方式实现数组求和的更多相关文章
- Java中实现多线程的四种方式
Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService.Cal ...
- Java中实现多线程的两种方式之间的区别
Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线 ...
- java中实现多线程的几种方式(简单实现)
一.以下只是简单的实现多线程 1:继承Thread 2:实现 Runnable 3:实现callable 如果需要返回值使用callable,如果不需要返回最好使用runnable,因为继承只能单继承 ...
- Java并发编程:Java实现多线程的几种方式
在Java中,多线程主要的实现方式有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService ...
- Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式
概要 本章,我们学习“常用的实现多线程的2种方式”:Thread 和 Runnable.之所以说是常用的,是因为通过还可以通过java.util.concurrent包中的线程池来实现多线程.关于线程 ...
- java多线程系类:基础篇:02常用的实现多线程的两种方式
本章,我们学习"常用的实现多线程的2种方式":Thread 和 Runnable.之所以说是常用的,是因为通过还可以通过java.util.concurrent包中的线程池来实现多 ...
- Java实现多线程的两种方式
实现多线程的两种方式: 方式1: 继承Thread类 A: 自定义MyThread类继承Thread类 B: 在MyThread类中重写run() C: 创建MyThread类的对象 D: 启动线程对 ...
- c#使用多线程的几种方式示例详解
本文转载自:http://www.jb51.net/article/46234.htm 本文章主要介绍了c#使用多线程的几种方式,通过示例学习c#的多线程使用方式,大家参考使用吧 (1)不需要传递参数 ...
- Java中HashMap遍历的两种方式
Java中HashMap遍历的两种方式 转]Java中HashMap遍历的两种方式原文地址: http://www.javaweb.cc/language/java/032291.shtml 第一种: ...
随机推荐
- 手机QQ架构的浅谈
手机QQ的原本的产品定位定位于移动社交,并将娱乐与生活服务相结合,整体的架构模块QQ主要分为登录注册,消息,聊天,联系人,动态,侧边栏,设置等几大模块.其中消息模块和聊天模块是核心模块.好友动态及联系 ...
- 分布式深度学习之DC-ASGD
本篇笔记是听刘铁岩老师做Distributed Deep Learning:New Driving Force of Artificial Intelligence报告整理而成 深度学习梯度下降公式如 ...
- SSH框架整合截图总结(三)
联系人信息查询1 点击 联系人信息查询 超链接时候,到查询页面 (1)在查询页面中,选择客户,根据客户进行查询 下拉表框显示所有客户 可以根据所属的客户进行联系人查询 2 在查询页面中,输入值,提 ...
- SpringMVC-HandlerMapping和HandlerAdapter
网上介绍HandlerMapping和HandlerAdapter的文章很多,今天我用自己的理解和语言来介绍下HandlerMapping和HandlerAdapter 一. HandlerMappi ...
- switch 的穿透, 以及穿透利用
switch 穿透测试: outputs: 添加break 阻止switch穿透: outputs: 利用switch的穿透功能:
- 看云-git类的书籍写作
看云-git类的书籍写作 https://www.kancloud.cn/explore 测试一本:https://www.kancloud.cn/stono/b001/501901
- HorizontalDragLayout-模仿QQclient的Item滑动删除
首先感谢http://blog.csdn.net/lmj623565791/article/details/46858663hongyang的文章.之前看过ViewDragHelper类也读过一些de ...
- linux 下password加密程序(能够用于替换shadow文件里的用户password)
源代码例如以下: #include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]){ if(arg ...
- C++类中静态变量和静态方法的注意事项
在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量仅仅存储一份供全部对象共用.所以在全部对象中都能够共享它.使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还能够 ...
- ios假设写一个提示带动画的View,能够来引导用户行为
先上图: 这个UIView能够这样写: -(id)initWithFrame:(CGRect)frame backImage:(UIImage*)image msgStr:(NSString*)txt ...