论坛首页 Java企业应用论坛

淘宝面试题:如何充分利用多核CPU,计算很大的List中所有整数的和

浏览 94257 次
该帖已经被评为良好帖
作者 正文
   发表时间:2010-07-14  
star022 写道
fork/join处理这个貌似是最合适的-----晕,一不小心发重复了,修改一下,顺便发个相关链接:
http://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/



这篇文章不错,谢了。

按照这个思路,其实子线程也可以分组求和最后再求和的,这样可能更快。
0 请登录后投票
   发表时间:2010-07-14  
高手还是很多的,哈哈,这个帖子有意思
0 请登录后投票
   发表时间:2010-07-14  
linliangyi2007 写道
高手还是很多的,哈哈,这个帖子有意思

嗯。。很多高手,关键时候都出来了。
0 请登录后投票
   发表时间:2010-07-14  
为什么要先分好呢?  而且这里线程数是否可以动态变化?
我意思让线程自己去BigList中去取数, 加完再取, 这样无论多少个线程, 也不会出现等待的情况, 直道再取不出数了即可.

又或者, 从bigList中每次取两个, 相加完成后放回bigList(可以把list构造成栈,FIFO), 这样无论多少线程,都好用了
0 请登录后投票
   发表时间:2010-07-14  
Joo 写道
为什么要先分好呢?  而且这里线程数是否可以动态变化?
我意思让线程自己去BigList中去取数, 加完再取, 这样无论多少个线程, 也不会出现等待的情况, 直道再取不出数了即可.

又或者, 从bigList中每次取两个, 相加完成后放回bigList(可以把list构造成栈,FIFO), 这样无论多少线程,都好用了


你说的意思应该是阻塞队列的那种情况,不过这样也有等待,当其中一个线程去取值的时候,其他的线程就要等着。

分割是为了分配任务,就像大家都敢一个活,你做这点,我做这点,咱俩互不影响,都干完了活也就完了。
0 请登录后投票
   发表时间:2010-07-14  
hardPass 写道
package com.wl.test.concurrent.semaphore;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 
 * 有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果。
 * 
 * 
 * 乍一看到题目“充分利用多核CPU”,
 * 以为会根据CPU的核心数,计算出比较合理的任务线程的数目。
 * 就题目的计算目标来说,实际上讲的主线程等待任务线程完成,
 * 任务线程之间没有必要互相等待。
 * 是不是可以考虑用信号来......
 * 每次来看帖,都发现大家图画的很拉风……
 * 
 * 我也帖个代码,虽然来晚了,大家不一定能看到……
 * 
 * @author HardPass
 *
 */
public class BigListSumWithSemaphore {
	private List<Integer> list = new ArrayList<Integer>();

	private AtomicLong sum = new AtomicLong(0L); // 结果
	private int threadCounts = 2 * Runtime.getRuntime().availableProcessors() + 1; // 任务线程数
	private List<Runnable> tasks = new ArrayList<Runnable>(); // 任务线程
	
	SemaphoreLock lock = new SemaphoreLock();
	


	public static void main(String[] args) {
		new BigListSumWithSemaphore().test();
	}

	public void test() {
		init();
		ExecutorService exec = Executors.newFixedThreadPool(threadCounts); //线程池
		for(Runnable task : tasks){
			exec.execute(task);
		}
		lock.lockHere(); // 此处尝试wait
		exec.shutdown();
		System.out.println("List中所有整数的和为:" + sum);
	}

	private void init() {
		for (int i = 1; i <= 1000000; i++) {
			list.add(i);
		}
		int len = list.size() / threadCounts;

		int i = 0;
		for (; i < threadCounts - 1; i++) {
			tasks.add(new Task(list.subList(i * len, (i + 1) * len)));
		}
		tasks.add(new Task(list.subList(i * len, list.size())));
	}

	private class Task implements Runnable {
		private List<Integer> subList;

		public Task(List<Integer> subList) {
			this.subList = subList;
		}

		@Override
		public void run() {
			lock.lockThere();  // 增加锁的计数
			long subSum = 0L;
			for (Integer i : subList) {
				subSum += i;
			}
			sum.addAndGet(subSum);
			System.out.println("分配给线程:" + Thread.currentThread().getName()
					+ "那一部分List的整数和为:\tSubSum:" + subSum);
			lock.release(); // 释放一个锁的计数
		}
	}

}

/**
 * 
 * 信号量锁
 * 
 * 此Semaphore非java.util.concurrent.Semaphore
 * 
 * @author HardPass
 *
 */
class SemaphoreLock {
	private int count = 0; // 信号量
	/**
	 * 信号量大于0的时候 wait
	 * 这是不是传说中的可重入?
	 */
	public synchronized void lockHere() {
		while (count > 0) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
	}

	public synchronized void lockThere() {
		count++;
	}

	public synchronized void release() {
		count--;
		if(count==0){
			notify();
		}
	}
}


对这个进行100次测试,发现有时候算出来的答案不正确,最后一个线程的结果没有累加进去,,,
5分配给线程:pool-1-thread-3那一部分List的整数和为: SubSum:100000100000
5分配给线程:pool-1-thread-1那一部分List的整数和为: SubSum:20000100000
5分配给线程:pool-1-thread-2那一部分List的整数和为: SubSum:60000100000
5分配给线程:pool-1-thread-4那一部分List的整数和为: SubSum:140000100000
===List中所有整数的和为:320000400000threadCounts:5
5分配给线程:pool-1-thread-5那一部分List的整数和为: SubSum:180000100000
代码:
sum.addAndGet(subSum);
System.out.println("分配给线程:" + Thread.currentThread().getName()
+ "那一部分List的整数和为:\tSubSum:" + subSum);
lock.release(); // 释放一个锁的计数
这个存在问题的?
代码理论由上往下执行,这个什么时候可能存在这样问题?(以前看单例双重检查加锁,new语句开辟内存区,但还未初始化数据,类似的?)
0 请登录后投票
   发表时间:2010-07-14   最后修改:2010-07-14
使用Gedit编辑的,难免有语法错误,呵呵
我的一个解法:
public class MyWorkThread extends Thread {
	private static BigDecimal sum;
	private List<Integer> list;
	private int start, end;
	private long value;

	public static BigDecimal getSum() {
		return sum;
	}

	public static synchronized void addSum(long v) {
		if (sum == null) {
			sum = new BigDecimal(v);
		} else {
			sum.add(BigDecimal.valueOf(v));
		}
	}

	
	public MyWorkThread(List<Integer> list, Integer start, Integer end) {
		this.list = list;
		this.start = start;
		this.end = end;
	}


	private void add(int v) {
		if (Long.MAX_VALUE - v > value) {
			value += v;
		} else {
			addSum(value);
			value = v;
		}
	}

	public void run() {
		for(int i = start; i < end; i++) add(list.get(i));
	}
	
	public static void main(String[] args) throws InterruptedException {
		List<Integer> list = new ArrayList<Integer>();
		int cpuCoreSize = 2;
		int len = list.size() / cpuCoreSize;
		int start = 0, end = len;
		for (;;) {
			end = start + len;
			if (end > list.size()) end = list.size();
			new MyWorkThread(list, start, end).start();
			start = end;
			if (start == list.size()) break;
		}
		Thread.currentThread().join();
		System.out.println("和为:" + MyWorkThread.getSum());
	}
}
0 请登录后投票
   发表时间:2010-07-14  
skzr.org 写道
使用Gedit编辑的,难免有语法错误,呵呵
我的一个解法:
public class MyWorkThread extends Thread {
	private static BigDecimal sum;
	private List<Integer> list;
	private int start, end;
	private long value;

	public static BigDecimal getSum() {
		return sum;
	}

	public static synchronized void addSum(long v) {
		if (sum == null) {
			sum = new BigDecimal(v);
		} else {
			sum.add(BigDecimal.valueOf(v));
		}
	}

	
	public MyWorkThread(List<Integer> list, Integer start, Integer end) {
		this.list = list;
		this.start = start;
		this.end = end;
	}


	private void add(int v) {
		if (Long.MAX_VALUE - v > value) {
			value += v;
		} else {
			addSum(value);
			value = v;
		}
	}

	public void run() {
		for(int i = start; i < end; i++) add(list.get(i));
	}
	
	public static void main(String[] args) throws InterruptedException {
		List<Integer> list = new ArrayList<Integer>();
		int cpuCoreSize = 2;
		int len = list.size() / cpuCoreSize;
		int start = 0, end = len;
		for (;;) {
			end = start + len;
			if (end > list.size()) end = list.size();
			new MyWorkThread(list, start, end).start();
			start = end;
			if (start == list.size()) break;
		}
		Thread.currentThread().join();
		System.out.println("和为:" + MyWorkThread.getSum());
	}
}


这个做法和楼主的差不多,在Java Collection,List#sublist方法,会创建一个新的java.util.SubList和它的子类,也是利用的偏移量来做的。
这里完全没有必要使用同步,使用AtomicLong就行了,如果大List是一个ArrayList的话,一个足够了,如果是LinkedList的实现,多个AtomicLong就行了。这里顺序不重要,利用Atomic*的内存CAS操作即可。
0 请登录后投票
   发表时间:2010-07-14  
很有意义的帖子,看来JE里真是卧虎藏龙啊,呵呵
0 请登录后投票
   发表时间:2010-07-14   最后修改:2010-07-14
dien 写道
hardPass 写道
package com.wl.test.concurrent.semaphore;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 
 * 有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果。
 * 
 * 
 * 乍一看到题目“充分利用多核CPU”,
 * 以为会根据CPU的核心数,计算出比较合理的任务线程的数目。
 * 就题目的计算目标来说,实际上讲的主线程等待任务线程完成,
 * 任务线程之间没有必要互相等待。
 * 是不是可以考虑用信号来......
 * 每次来看帖,都发现大家图画的很拉风……
 * 
 * 我也帖个代码,虽然来晚了,大家不一定能看到……
 * 
 * @author HardPass
 *
 */
public class BigListSumWithSemaphore {
	private List<Integer> list = new ArrayList<Integer>();

	private AtomicLong sum = new AtomicLong(0L); // 结果
	private int threadCounts = 2 * Runtime.getRuntime().availableProcessors() + 1; // 任务线程数
	private List<Runnable> tasks = new ArrayList<Runnable>(); // 任务线程
	
	SemaphoreLock lock = new SemaphoreLock();
	


	public static void main(String[] args) {
		new BigListSumWithSemaphore().test();
	}

	public void test() {
		init();
		ExecutorService exec = Executors.newFixedThreadPool(threadCounts); //线程池
		for(Runnable task : tasks){
			exec.execute(task);
		}
		lock.lockHere(); // 此处尝试wait
		exec.shutdown();
		System.out.println("List中所有整数的和为:" + sum);
	}

	private void init() {
		for (int i = 1; i <= 1000000; i++) {
			list.add(i);
		}
		int len = list.size() / threadCounts;

		int i = 0;
		for (; i < threadCounts - 1; i++) {
			tasks.add(new Task(list.subList(i * len, (i + 1) * len)));
		}
		tasks.add(new Task(list.subList(i * len, list.size())));
	}

	private class Task implements Runnable {
		private List<Integer> subList;

		public Task(List<Integer> subList) {
			this.subList = subList;
		}

		@Override
		public void run() {
			lock.lockThere();  // 增加锁的计数
			long subSum = 0L;
			for (Integer i : subList) {
				subSum += i;
			}
			sum.addAndGet(subSum);
			System.out.println("分配给线程:" + Thread.currentThread().getName()
					+ "那一部分List的整数和为:\tSubSum:" + subSum);
			lock.release(); // 释放一个锁的计数
		}
	}

}

/**
 * 
 * 信号量锁
 * 
 * 此Semaphore非java.util.concurrent.Semaphore
 * 
 * @author HardPass
 *
 */
class SemaphoreLock {
	private int count = 0; // 信号量
	/**
	 * 信号量大于0的时候 wait
	 * 这是不是传说中的可重入?
	 */
	public synchronized void lockHere() {
		while (count > 0) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
	}

	public synchronized void lockThere() {
		count++;
	}

	public synchronized void release() {
		count--;
		if(count==0){
			notify();
		}
	}
}


对这个进行100次测试,发现有时候算出来的答案不正确,最后一个线程的结果没有累加进去,,,
5分配给线程:pool-1-thread-3那一部分List的整数和为: SubSum:100000100000
5分配给线程:pool-1-thread-1那一部分List的整数和为: SubSum:20000100000
5分配给线程:pool-1-thread-2那一部分List的整数和为: SubSum:60000100000
5分配给线程:pool-1-thread-4那一部分List的整数和为: SubSum:140000100000
===List中所有整数的和为:320000400000threadCounts:5
5分配给线程:pool-1-thread-5那一部分List的整数和为: SubSum:180000100000
代码:
sum.addAndGet(subSum);
System.out.println("分配给线程:" + Thread.currentThread().getName()
+ "那一部分List的整数和为:\tSubSum:" + subSum);
lock.release(); // 释放一个锁的计数
这个存在问题的?
代码理论由上往下执行,这个什么时候可能存在这样问题?(以前看单例双重检查加锁,new语句开辟内存区,但还未初始化数据,类似的?)



没这么复杂
应该是主线程执行太快,任务线程还没来得及锁导致的。

这个东西本来是想模拟CountDownLatch的

仔细想了下,这个设计还会有很大的问题,如果其中某个或者某些任务线程起的太快,并且完成的也快,会导致锁计数很快增加并很快减为0,并且在这个时候,还有一些任务线程还没有启动……
解决方法也不是没有,可以将加锁放在主线程,任务线程只release^


还是用CountDownLatch 比较稳妥

ExecuteService invokeall callable 也是推荐的方法


0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics