当前位置: 首页 > news >正文

有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果(转)...

 

引用
前几天在网上看到一个淘宝的面试题:有一个很大的整数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)

 

相关文章:

  • 实习小白::(转) Cocos2d-x 3.0 开发(十二)在CocoStudio中使用粒子挂载与曲线动画...
  • 高焕堂讲Android核心
  • python对象的创建和销毁
  • Foundation框架集合 ---- NSArray和NSMutableArray
  • 【BZOJ】2876: [Noi2012]骑行川藏
  • 【BZOJ】3714: [PA2014]Kuglarz
  • intelliJ IDEA使用体验
  • 网络工程实训_4RIP路由(动态路由)
  • LLC子层为什么不在数据包中体现?LLC子层具体作用是什么?Ethernet_II如何表示帧结束?...
  • linux下建立软链接
  • 享元模式 的另外一个例证
  • windows ntp安装及调试
  • MYSQL 备份用户权限
  • A7600官方ROM_VIBEUI_V2.5_1537联通版使用体验
  • 《FPGA全程进阶---实战演练》第二章之焊接板子及调试注意事项
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【comparator, comparable】小总结
  • 【Linux系统编程】快速查找errno错误码信息
  • CSS中外联样式表代表的含义
  • extjs4学习之配置
  • happypack两次报错的问题
  • JavaScript标准库系列——Math对象和Date对象(二)
  • JavaScript服务器推送技术之 WebSocket
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • Linux各目录及每个目录的详细介绍
  • Next.js之基础概念(二)
  • SpiderData 2019年2月16日 DApp数据排行榜
  • Transformer-XL: Unleashing the Potential of Attention Models
  • 基于游标的分页接口实现
  • 如何进阶一名有竞争力的程序员?
  • 转载:[译] 内容加速黑科技趣谈
  • ​HTTP与HTTPS:网络通信的安全卫士
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • (1)bark-ml
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (第二周)效能测试
  • (多级缓存)缓存同步
  • (二)hibernate配置管理
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (力扣)1314.矩阵区域和
  • (转)Google的Objective-C编码规范
  • (转)德国人的记事本
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • /使用匿名内部类来复写Handler当中的handlerMessage()方法
  • @Conditional注解详解
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [ 数据结构 - C++] AVL树原理及实现
  • [1] 平面(Plane)图形的生成算法
  • [Android]使用Git将项目提交到GitHub
  • [CLickhouse] 学习小计
  • [GDOUCTF 2023]<ez_ze> SSTI 过滤数字 大括号{等