有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果(转)
一:分析题目
从题中可以看到“很大的List”以及“充分利用多核CPU”,这就已经充分告诉我们要采用多线程(任务)进行编写。具体怎么做呢?大概的思路就是分割List,每一小块的List采用一个线程(任务)进行计算其和,最后等待所有的线程(任务)都执行完后就可得到这个“很大的List”中所有整数的和。
二:具体分析和技术方案
既然我们已经决定采用多线程(任务),并且还要分割List,每一小块的List采用一个线程(任务)进行计算其和,那么我们必须要等待所有的线程(任务)完成之后才能得到正确的结果,那么怎么才能保证“等待所有的线程(任务)完成之后输出结果呢”?这就要靠java.util.concurrent包中的CyclicBarrier类了。它是一个同步辅助类,它允许一组线程(任务)互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程(任务)的程序中,这些线程(任务)必须不时地互相等待,此时 CyclicBarrier 很有用。简单的概括其适应场景就是:当一组线程(任务)并发的执行一件工作的时候,必须等待所有的线程(任务)都完成时才能进行下一个步骤。具体技术方案步骤如下:
- 分割List,根据采用的线程(任务)数平均分配,即list.size()/threadCounts。
- 定义一个记录“很大List”中所有整数和的变量sum,采用一个线程(任务)处理一个分割后的子List,计算子List中所有整数和(subSum),然后把和(subSum)累加到sum上。
- 等待所有线程(任务)完成后输出总和(sum)的值。
示意图如下: (Edraw)
三:详细编码实现
代码中有很详细的注释,这里就不解释了。
/**
* 计算List中所有整数的和<br>
* 采用多线程,分割List计算
*/
public class CountListIntegerSum {
private long sum;//存放整数的和
private CyclicBarrier barrier;//障栅集合点(同步器)
private List<Integer> list;//整数集合List
private int threadCounts;//使用的线程数
public CountListIntegerSum(List<Integer> list,int threadCounts) {
this.list=list;
this.threadCounts=threadCounts;
}
/**
* 获取List中所有整数的和
* @return
*/
public long getIntegerSum(){
ExecutorService exec=Executors.newFixedThreadPool(threadCounts);
int len=list.size()/threadCounts;//平均分割List
//List中的数量没有线程数多(很少存在)
if(len==0){
threadCounts=list.size();//采用一个线程处理List中的一个元素
len=list.size()/threadCounts;//重新平均分割List
}
barrier=new CyclicBarrier(threadCounts+1);
for(int i=0;i<threadCounts;i++){
//创建线程任务
if(i==threadCounts-1){//最后一个线程承担剩下的所有元素的计算
exec.execute(new SubIntegerSumTask(list.subList(i*len,list.size())));
}else{
exec.execute(new SubIntegerSumTask(list.subList(i*len, len*(i+1)>list.size()?list.size():len*(i+1))));
}
}
try {
barrier.await();//关键,使该线程在障栅处等待,直到所有的线程都到达障栅处
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+":Interrupted");
} catch (BrokenBarrierException e) {
System.out.println(Thread.currentThread().getName()+":BrokenBarrier");
}
exec.shutdown();
return sum;
}
/**
* 分割计算List整数和的线程任务
*
*/
public class SubIntegerSumTask implements Runnable{
private List<Integer> subList;
public SubIntegerSumTask(List<Integer> subList) {
this.subList=subList;
}
public void run() {
long subSum=0L;
for (Integer i : subList) {
subSum += i;
}
synchronized(CountListIntegerSum.this){//在CountListIntegerSum对象上同步
sum+=subSum;
}
try {
barrier.await();//关键,使该线程在障栅处等待,直到所有的线程都到达障栅处
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+":Interrupted");
} catch (BrokenBarrierException e) {
System.out.println(Thread.currentThread().getName()+":BrokenBarrier");
}
System.out.println("分配给线程:"+Thread.currentThread().getName()+"那一部分List的整数和为:\tSubSum:"+subSum);
} } }
有人可能对barrier=new CyclicBarrier(threadCounts+1);//创建的线程数和主线程main有点不解,不是采用的线程(任务)数是threadCounts个吗?怎么为CyclicBarrier设置的给定数量的线程参与者比我们要采用的线程数多一个呢?
答案就是这个多出来的一个用于控制main主线程的,主线程也要等待,它要等待其他所有的线程完成才能输出sum值,这样才能保证sum值的正确性,如果main不等待的话,那么结果将是不可预料的。
/**
* 计算List中所有整数的和测试类
*/
public class CountListIntegerSumMain { /**
* @param args
*/
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
int threadCounts = 10;//采用的线程数
//生成的List数据
for (int i = 1; i <= 1000000; i++) {
list.add(i);
}
CountListIntegerSum countListIntegerSum=new CountListIntegerSum(list,threadCounts);
long sum=countListIntegerSum.getIntegerSum();
System.out.println("List中所有整数的和为:"+sum);
} }
四:总结
本文主要通过一个淘宝的面试题为引子,介绍了并发的一点小知识,主要是介绍通过CyclicBarrier同步辅助器辅助多个并发任务共同完成一件工作。Java SE5的java.util.concurrent引入了大量的设计来解决并发问题,使用它们有助于我们编写更加简单而健壮的并发程序。
附mathfox提到的ExecutorService.invokeAll()方法的实现
这个不用自己控制等待,invokeAll执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。sdh5724也说用了同步,性能不好。这个去掉了同步,根据返回结果的 Future 列表相加就得到总和了。
/**
* 使用ExecutorService的invokeAll方法计算
*
*/
public class CountSumWithCallable { /**
* @param args
* @throws InterruptedException
* @throws ExecutionException
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
int threadCounts =19;//使用的线程数
long sum=0;
ExecutorService exec=Executors.newFixedThreadPool(threadCounts);
List<Callable<Long>> callList=new ArrayList<Callable<Long>>();
//生成很大的List
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i <= 1000000; i++) {
list.add(i);
}
int len=list.size()/threadCounts;//平均分割List
//List中的数量没有线程数多(很少存在)
if(len==0){
threadCounts=list.size();//采用一个线程处理List中的一个元素
len=list.size()/threadCounts;//重新平均分割List
}
for(int i=0;i<threadCounts;i++){
final List<Integer> subList;
if(i==threadCounts-1){
subList=list.subList(i*len,list.size());
}else{
subList=list.subList(i*len, len*(i+1)>list.size()?list.size():len*(i+1));
}
//采用匿名内部类实现
callList.add(new Callable<Long>(){
public Long call() throws Exception {
long subSum=0L;
for(Integer i:subList){
subSum+=i;
}
System.out.println("分配给线程:"+Thread.currentThread().getName()+"那一部分List的整数和为:\tSubSum:"+subSum);
return subSum;
}
});
}
List<Future<Long>> futureList=exec.invokeAll(callList);
for(Future<Long> future:futureList){
sum+=future.get();
}
exec.shutdown();
System.out.println(sum);
} }
一些感言
这篇文章是昨天夜里11点多写好的,我当时是在网上看到了这个题目,就做了一下分析,写了实现代码,由于水平有限,难免有bug,这里感谢xifo等人的指正。这些帖子从发表到现在不到24小时的时间里创造了近9000的浏览次数,回复近100,这是我没有想到的,javaeye很久没这么疯狂过啦。这不是因为我的算法多好,而是因为这个题目、这篇帖子所体现出的意义。大家在看完这篇帖子后不光指正错误,还对方案进行了改进,关键是思考,人的思维是无穷的,只要我们善于发掘,善于思考,总能想出一些意想不到的方案。
从算法看,或者从题目场景对比代码实现来看,或许不是一篇很好的帖子,但是我说这篇帖子是很有意义的,方案也是在很多场景适用,有时我们可以假设这不是计算和,而是把数据写到一个个的小文件里,或者是分割进行网络传输等等,都有一定的启发,特别是回帖中的讨论。
单说一下回帖,我建议进来的人尽量看完所有的回帖,因为这里是很多人集思广益的精华,这里有他们分析问题,解决问题的思路,还有每个人提到的解决方案,想想为什么能用?为什么不能用?为什么好?为什么不好?
我一直相信:讨论是解决问题、提高水平的最佳方式!
http://www.iteye.com/topic/711162
InvokeAll的一个demo
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; public class InvokeAllDemoWhenException {
public static void main(String[] args) { List<TaskInvokeAll> tasks = new ArrayList<TaskInvokeAll>();
for (int i = 0; i < 11; i++) {
tasks.add(new TaskInvokeAll(i));
} ExecutorService es = Executors.newCachedThreadPool();
try {
List<Future<String>> invokeAll = es.invokeAll(tasks);
for (Future<String> future : invokeAll) {
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} } class TaskInvokeAll implements Callable<String> { private int flag; public TaskInvokeAll(int flag) {
this.flag = flag;
} @Override
public String call() throws Exception {
Thread currentThread = Thread.currentThread();
if (flag == 5) {
System.out.println(currentThread + " " + this.flag);
throw new IllegalArgumentException("current :" + flag);
}
if (flag == 2) {
for (int i = 0; i < 5; i++) {
System.out.println(currentThread + "Begin to sleep 1s");
TimeUnit.SECONDS.sleep(1);
}
}
System.out.println(currentThread + " T'm finish:" + this.flag);
return String.valueOf(flag);
} }
Output(抛异常的打印,在输出结果时不固定):
Thread[pool-1-thread-1,5,main] T'm finish:0
Thread[pool-1-thread-5,5,main] T'm finish:4
Thread[pool-1-thread-2,5,main] T'm finish:1
Thread[pool-1-thread-3,5,main]Begin to sleep 1s
Thread[pool-1-thread-5,5,main] T'm finish:10
Thread[pool-1-thread-7,5,main] T'm finish:6
Thread[pool-1-thread-9,5,main] T'm finish:8
Thread[pool-1-thread-4,5,main] T'm finish:3
Thread[pool-1-thread-8,5,main] T'm finish:7
Thread[pool-1-thread-6,5,main] 5
Thread[pool-1-thread-2,5,main] T'm finish:9
Thread[pool-1-thread-3,5,main]Begin to sleep 1s
Thread[pool-1-thread-3,5,main]Begin to sleep 1s
Thread[pool-1-thread-3,5,main]Begin to sleep 1s
Thread[pool-1-thread-3,5,main]Begin to sleep 1s
Thread[pool-1-thread-3,5,main] T'm finish:2
0
1
2
3
4
6
7
8
9
10
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: current :5
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222)
at java.util.concurrent.FutureTask.get(FutureTask.java:83)
at thread.InvokeAllDemoWhenException.main(InvokeAllDemoWhenException.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.IllegalArgumentException: current :5
at thread.TaskInvokeAll.call(InvokeAllDemoWhenException.java:50)
at thread.TaskInvokeAll.call(InvokeAllDemoWhenException.java:37)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果(转)的更多相关文章
- string::npos,一个很大的数
string::npos,这是一个很大的数 npos 是这样定义的: static const size_type npos = -1; 因为 string::size_type (由字符串配置器 a ...
- IoC是一个很大的概念,可以用不同的方式实现。
IoC是一个很大的概念,可以用不同的方式实现.其主要形式有两种: ◇ 依赖查找:容器提供回调接口和上下文条件给组件.EJB和Apache Avalon 都使用这种方式.这样一来,组件就必须使用容器提供 ...
- IoC是一个很大的概念,可以用不同的方式实现。其主要形式有两种:
IoC是一个很大的概念,可以用不同的方式实现.其主要形式有两种: ◇依赖查找:容器提供回调接口和上下文条件给组件.EJB和ApacheAvalon都使用这种方式.这样一来,组件就必须使用容器提供的AP ...
- 如何用 php 读取一个很大的 excel 文件。
这个程序是用php 读取一个很大的excel文件, 先将 excel 文件保存成csv 文件, 然后利用 迭代器 逐行读取 excel 单元格的值, 拿到值以后 做相应处理,并打印结果. <?p ...
- node.js 如何处理一个很大的文件
node.js 如何处理一个很大的文件 思路 arraybuffer 数据分段 时间分片 多线程 web workers sevice workers node.js 如何处理一个很大的文件 http ...
- JS事件 编程练习-自制计算器 使用JS完成一个简单的计算器功能。实现2个输入框中输入整数后,点击第三个输入框能给出2个整数的加减乘除。
编程练习 使用JS完成一个简单的计算器功能.实现2个输入框中输入整数后,点击第三个输入框能给出2个整数的加减乘除. 提示:获取元素的值设置和获取方法为:例:赋值:document.getElement ...
- W - Doom HDU - 5239 线段树 找取模的规律+求一个很大的数的平方对一个数取模的写法 特别的模数==2^63-2^31
这个题目一开始感觉还是有点难的,这个模数这么大,根本就不知道怎么写,然后去搜了题解,知道了怎么去求当x很大的时候x的平方对一个数取模怎么样不会爆掉. 然后还顺便发现了一个规律就是当一个数更新一定次数之 ...
- 创建一个很大的EMP表 EMP_LARGE
--CREATE TABLE EMP_LARGE AS SELECT * FROM EMP ; ---先复制一张EMP表 DECLARE --声明变量 v_loop NUMBER; v_num NUM ...
- 一个很大的文件,存放了10G个整数的乱序数列,如何用程序找出中位数。
一.梳理审题 一.看清题目: 注意这个题目的量词,这个文件中有10G个整数,而不是这个文件占了10G的内存空间. 二.一些疑问: 在计算机中我们讲的G.M等都是存储容量的概念,但是一般都会在会面加上B ...
随机推荐
- Just4Fun - Comparaison between const and readonly in C#
/* By Dylan SUN */ Today let us talk about const and readonly. const is considered as compile-time c ...
- Java OCR tesseract 图像智能字符识别技术 Java代码实现
接着上一篇OCR所说的,上一篇给大家介绍了tesseract 在命令行的简单用法,当然了要继承到我们的程序中,还是需要代码实现的,下面给大家分享下java实现的例子. 拿代码扫描上面的图片,然后输出结 ...
- javascript有用小技巧—实现分栏显示
记得给师哥师姐測试考试系统的时候,看到他们的考试页面能够实现隐藏左边的考生信息部分,当时认为好高大上.好人性化. 如今学了javascript,我也能实现这个功能了,以下来显摆一下. 1.页面设计: ...
- android_定义多个Activity及跳转
说明:在Android应用程序其中创建多个activity,而且启动一个activity的方法,以及activity之间的跳转. 样例:在MainActivity里面加入一个button,触动butt ...
- ASP.NET MVC 5 入门摘要
翻译和编辑的第一阶段后,.我们已经发出12片ASP.NET MVC 5入门文章. 他们中的大多数来自翻译ASP.NET MVC 5 官方教程,因为本系列文章言简意赅,篇幅适中,从一个web站点演示样例 ...
- MonkenRunner通过HierarchyViewer定位控制的方法和建议(Appium/UIAutomator/Robotium侣)
1. 背景 正在使用MonkeyRunner当我们经常使用Chimchat下面HierarchyViewer模块获得目标控制的一些信息,以协助我们测试.但在MonkeyRunner官方的说法是没有看到 ...
- Make a dent in the universe
李自成<一个数学家平反>.就像我第一次读同一,我感到鼓舞的野心文."野心是世界的驱动力的最好的结果几乎所有的工作. " 心野心是要留下点什么在自己身后的永恒价值 在这个 ...
- 作为一个新人,如何学习嵌入式Linux?
作为一个新人.如何学习嵌入式Linux?我一直在问太多次,特写文章来回答这个问题. 在学习嵌入式Linux之前.肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).C语言要 ...
- 第三篇——第二部分——第三文 配置SQL Server镜像——域环境
原文:第三篇--第二部分--第三文 配置SQL Server镜像--域环境 原文出处:http://blog.csdn.net/dba_huangzj/article/details/28904503 ...
- EJB(一)认识ejb
什么是ejb? 相同仍旧在这个系列博客之处,谈谈对ejb的认识和理解. sun微公司对于ejb的定义大体是这种,ejb是一套用于开发和部署分布式组件的的架构.採用ejb的架构应用能够是 ...