轉載java_eye(http://www.iteye.com/topic/711162)
一:分析題目
從題中可以看到“很大的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)的值。 </UL>
- /**
- * 計算List中所有整數的和<br>
- * 采用多線程,分割List計算
- * @author 飛雪無情
- * @since 2010-7-12
- */
- 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整數和的線程任務
- * @author lishuai
- *
- */
- 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);
- }
- }
- } </OL></DIV><PRE style="DISPLAY: none" class=java title=淘寶面試題:如何充分利用多核CPU,計算很大的List中所有整數的和 pre_index="0" source_url="
- 計算List中所有整數的和<br>
- 采用多線程,分割List計算
- @author 飛雪無情
@since 2010-7-12 */ 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){
} barrier=new CyclicBarrier(threadCounts+1); for(int i=0;i<threadCounts;i++){threadCounts=list.size();//采用一個線程處理List中的一個元素 len=list.size()/threadCounts;//重新平均分割List
} try {//創建線程任務 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)))); }
} catch (InterruptedException e) {barrier.await();//關鍵,使該線程在障柵處等待,直到所有的線程都到達障柵處
} catch (BrokenBarrierException e) {System.out.println(Thread.currentThread().getName()+":Interrupted");
} exec.shutdown(); return sum; } /**System.out.println(Thread.currentThread().getName()+":BrokenBarrier"); - 分割計算List整數和的線程任務
@author lishuai / 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);}
}
- /**
- * 計算List中所有整數的和測試類
- * @author 飛雪無情
- * @since 2010-7-12
- */
- 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);
- }
- } </OL></DIV><PRE style="DISPLAY: none" class=java title=淘寶面試題:如何充分利用多核CPU,計算很大的List中所有整數的和 pre_index="1" source_url="
- 計算List中所有整數的和測試類
- @author 飛雪無情
@since 2010-7-12 */ 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++) {
} CountListIntegerSum countListIntegerSum=new CountListIntegerSum(list,threadCounts); long sum=countListIntegerSum.getIntegerSum(); System.out.println("List中所有整數的和為:"+sum); }list.add(i);
- @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++) {
- /**
- * 使用ExecutorService的invokeAll方法計算
- * @author 飛雪無情
- *
- */
- 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);
- }
- } </OL></DIV><PRE style="DISPLAY: none" class=java title=淘寶面試題:如何充分利用多核CPU,計算很大的List中所有整數的和 pre_index="2" source_url="
- 使用ExecutorService的invokeAll方法計算
@author 飛雪無情 / 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++) {
} int len=list.size()/threadCounts;//平均分割List //List中的數量沒有線程數多(很少存在) if(len==0){list.add(i);
} for(int i=0;i<threadCounts;i++){threadCounts=list.size();//采用一個線程處理List中的一個元素 len=list.size()/threadCounts;//重新平均分割List
} List<Future<Long>> futureList=exec.invokeAll(callList); for(Future<Long> future:futureList){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; } });
} exec.shutdown(); System.out.println(sum); }sum+=future.get();
示意圖如下:
<IMG class=magplus title=點擊查看原始大小圖片 src="三:詳細編碼實現
代碼中有很詳細的注釋,這里就不解釋了。
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代碼 <A title=復制代碼 href="/misc/goto?guid=5033827611214704784"><IMG alt=復制代碼 src="</A> <A title=收藏這段代碼 href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="<IMG style="DISPLAY: none" class=spinner src="</A></DIV></DIV>
<OL class=dp-j>
}</PRE>
有人可能對barrier=new CyclicBarrier(threadCounts+1);//創建的線程數和主線程main有點不解,不是采用的線程(任務)數是threadCounts個嗎?怎么為CyclicBarrier設置的給定數量的線程參與者比我們要采用的線程數多一個呢?答案就是這個多出來的一個用于控制main主線程的,主線程也要等待,它要等待其他所有的線程完成才能輸出sum值,這樣才能保證sum值的正確性,如果main不等待的話,那么結果將是不可預料的。
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代碼 <A title=復制代碼 href="/misc/goto?guid=5033827611214704784"><IMG alt=復制代碼 src="</A> <A title=收藏這段代碼 href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="<IMG style="DISPLAY: none" class=spinner src="</A></DIV></DIV>
<OL class=dp-j>
}</PRE>
四:總結
本文主要通過一個淘寶的面試題為引子,介紹了并發的一點小知識,主要是介紹通過CyclicBarrier同步輔助器輔助多個并發任務共同完成一件工作。Java SE5的java.util.concurrent引入了大量的設計來解決并發問題,使用它們有助于我們編寫更加簡單而健壯的并發程序。
附mathfox提到的ExecutorService.invokeAll()方法的實現
這個不用自己控制等待,invokeAll執行給定的任務,當所有任務完成時,返回保持任務狀態和結果的 Future 列表。sdh5724也說用了同步,性能不好。這個去掉了同步,根據返回結果的 Future 列表相加就得到總和了。
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代碼 <A title=復制代碼 href="/misc/goto?guid=5033827611214704784"><IMG alt=復制代碼 src="</A> <A title=收藏這段代碼 href="/misc/goto?guid=5033824541631065302"><IMG class=star alt=收藏代碼 src="<IMG style="DISPLAY: none" class=spinner src="</A></DIV></DIV>
<OL class=dp-j>
}</PRE>