论坛首页 Java企业应用论坛

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

浏览 94271 次
该帖已经被评为良好帖
作者 正文
   发表时间:2010-07-15  
如果只是考CyclicBarrier的使用,那没接触过的人不就不会了.
0 请登录后投票
   发表时间:2010-07-15   最后修改:2010-07-15
仔细研究了下Thread.currentThread().join();
当前线程等待当前线程结束?




	    while (isAlive()) {
	       wait(0);
	    }


实际上是当前线程等待自己die:因为一直等待,所以无法die;因为没有die,所以还在wait(0)

如果企图在其他线程Notify,会报java.lang.IllegalMonitorStateException,同时,它还在无限制的等待自己die


所以这个代码非常恶劣霸道!

会导致当前线程永久性地睡眠,并且没有任何办法打断。



final Thread mainThread = Thread.currentThread();
		Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("------");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("------");
				mainThread.notify();
			}
		});
		
		t.start();
		try {
			//t.join();
			//Thread.currentThread().join(1000);
			Thread.currentThread().join();
			System.out.println("++");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}











0 请登录后投票
   发表时间:2010-07-15  
我觉得还可以优化下
    public static BigInteger g(List<Integer> list, int k) throws InterruptedException {
        int size = list.size();
        if (size == 0 || k < 1) {
            throw new RuntimeException();
        }
        k = k <= size ? k : size;
        int a = (size + k - 1) / k;
        Test[] t = new Test[k - 1];
        for (int i = 0; i < t.length; i++) {
            t[i] = new Test(list.subList(a * i, a * i + a));
            t[i].start();
        }
        Test c = new Test(list.subList(a * (k - 1), size));
        c.run();
        BigInteger sum = c.sum;
        for (int i = 0; i < t.length; i++) {
            t[i].join();
            sum = sum.add(t[i].sum);
        }
        return sum;
    }

    private static class Test extends Thread {

        public Test(List<Integer> list) {
            this.list = list;
            sum = BigInteger.ZERO;
            _sum = 0;
        }

        @Override
        public void run() {
            for (Integer i : list) {
                if (i == 0) {
                    continue;
                }
                if (i > 0 ? (_sum + i < _sum) : (_sum + i > _sum)) {
                    sum = sum.add(BigInteger.valueOf(_sum));
                    _sum = 0;
                }
                _sum += i;
            }
            sum = sum.add(BigInteger.valueOf(_sum));
        }
        private List<Integer> list;
        private BigInteger sum;
        private long _sum;
    }
0 请登录后投票
   发表时间:2010-07-15  
lucky16 写道
hrsvici412 写道
学到好多线程的东西,请问 你的分析的图,是用什么工具画的!

好像是office2007or2010 里面有一个好像就叫流程图的东西~


是指的visio吗?画流程图没问题,就是不太好看,没有楼主的那么艳丽
0 请登录后投票
   发表时间:2010-07-15  
hardPass 写道
仔细研究了下Thread.currentThread().join();
当前线程等待当前线程结束?




	    while (isAlive()) {
	       wait(0);
	    }


实际上是当前线程等待自己die:因为一直等待,所以无法die;因为没有die,所以还在wait(0)

如果企图在其他线程Notify,会报java.lang.IllegalMonitorStateException,同时,它还在无限制的等待自己die


所以这个代码非常恶劣霸道!

会导致当前线程永久性地睡眠,并且没有任何办法打断。



final Thread mainThread = Thread.currentThread();
		Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("------");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("------");
				mainThread.notify();
			}
		});
		
		t.start();
		try {
			//t.join();
			//Thread.currentThread().join(1000);
			Thread.currentThread().join();
			System.out.println("++");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}












嗯。。相互等待,死锁。。
0 请登录后投票
   发表时间:2010-07-15  
本质上是一个分治思想,google的map/reduce也是这么个意思。但是有个问题,以前我们在实际运用中使用类似的方案时,发现服务器的CPU并不是都在忙碌状态中,也就是说Java的多线程技术并不是一定能够充分利用到服务器的所有CPU资源。具体原因还不得而知,我怀疑是jvm的底层实现造成的。毕竟UNIX系统根本就没有线程的概念。还望有大牛能够解释一下,谢谢
0 请登录后投票
   发表时间:2010-07-15  
使用ExecutorService.invokeAll的缺点是,如果第一个任务需要花很多时间,可能不得不等待
使用这个会一个一个的遍历,如果某个任务还没有完成就会等待

应该使用ExecutorCompleteionService,会直接获取下一个已经完成的task,如果没有任何一个task是完成的话,再等待。
0 请登录后投票
   发表时间:2010-07-15   最后修改:2010-07-15
lanxiazhi 写道
看过主贴和评论,学习了。
发现的问题:
(1)CyclicBarrier不适合这种并发任务。单个线程完成任务之后完全可以终止了,没必要全部等待着,这可能是很大的资源浪费。使用CountDownLatch也会有这个问题。
(2)楼主使用了线程同步,考虑到同步的代价,这是可能是个很大的时间浪费。
(3)楼主使用CyclicBarrier的唯一用处在于,保证所有的任务都完成了。但是杀鸡焉用牛刀?这可能是...的浪费

  其实实现这样的功能,可以不用那么复杂,而且可以不用加锁。这里需要一个AtomicInteger(设为atom)。每个线程获得自己的sublist求和任务之后,计算一个和,保存到某个成员变量(设为subsum)中(如某楼上所说),把那个atom加1,然后结束。
  在主方法中,启动所有线程,然后添加一条语句,等待所有线程完成任务:
while(atom.intValue()<threadCounts)
  Thread.yield();
最后从所有死掉的线程对象中,获取subsum并累加即可。




我比较同意lanxianzhi的说法。
使用CountDownLatch,其他所有的计算线程都在计算,最后输出的线程肯定在等所有的线程算好才行。
更进一步的问题是:如果我想知道目前算好的实时总数是多少,以及目前多少个线程已经算好了,那么使用CountDownLatch是没有办法的。
比如,有10000个计算线程,一个输出线程在wait,输出线程只能等10000个计算线程都计算完,wait才会结束,然后才能输出。只有一次输出。

也许你想在计算的线程里面把目前被同步的sum给实时打印出来,这样每个计算线程计算结束之后就把当时的实时的结果打印出来,那么计算线程就不符合单一责任原则了。

对于目前已经完成多少个任务,再加个线程共享的变量(任务完成计数器)来自增也不好。
就算用上AtomicInteger也是一种负担。

所以我不觉得应该使用CountDownLatch

线程同步synchronized是不需要的,用AtomicInteger会快一些。

CyclicBarrier就更不需要了,我没有写代码测试,但是按照我的理解,如果你开启11个线程,10个线程在计算,1个线程等待输出,只有当所有线程都执行到await()才能继续。
如果你开的线程就上千个,那么且不是上千个线程都在那边等待?

我还是推荐使用ExecutorCompleteionService

1. ExecutorCompleteionService可以随时拿到完成计算的线程的结果,然后在这个基础上继续进行加运算,而且不需要创建一个在线程之间共享的变量,每个线程有自己的结果变量,这个变量最终会作为task的结果之一返回。
2. 可以随时知道完成的计算线程的计数,当然需要ExecutorCompleteionService执行够快才行,如果计算线程执行很快就结束的话,完成的线程数会不准

我没有在机子上试
贴个Java核心技术的例子代码
ExecutorCompleteionService service = new ExecutorCompleteionService(executor);
for(Callable<Integer> task : tasks) {
    service.submit(task);
}
for(int i = 0; i < tasks.size(); i++) {
    count += service.take().get();// 如果有线程返回,马上可以得到结果,如果没有任何线程返回,阻塞等待结果
}
这里不需要sum这个所有的线程都共享的变量。因此也不需要同步或者AtomicInteger。

0 请登录后投票
   发表时间:2010-07-16  
linchao198401 写道
lanxiazhi 写道
看过主贴和评论,学习了。
发现的问题:
(1)CyclicBarrier不适合这种并发任务。单个线程完成任务之后完全可以终止了,没必要全部等待着,这可能是很大的资源浪费。使用CountDownLatch也会有这个问题。
(2)楼主使用了线程同步,考虑到同步的代价,这是可能是个很大的时间浪费。
(3)楼主使用CyclicBarrier的唯一用处在于,保证所有的任务都完成了。但是杀鸡焉用牛刀?这可能是...的浪费

  其实实现这样的功能,可以不用那么复杂,而且可以不用加锁。这里需要一个AtomicInteger(设为atom)。每个线程获得自己的sublist求和任务之后,计算一个和,保存到某个成员变量(设为subsum)中(如某楼上所说),把那个atom加1,然后结束。
  在主方法中,启动所有线程,然后添加一条语句,等待所有线程完成任务:
while(atom.intValue()<threadCounts)
  Thread.yield();
最后从所有死掉的线程对象中,获取subsum并累加即可。




我比较同意lanxianzhi的说法。
使用CountDownLatch,其他所有的计算线程都在计算,最后输出的线程肯定在等所有的线程算好才行。
更进一步的问题是:如果我想知道目前算好的实时总数是多少,以及目前多少个线程已经算好了,那么使用CountDownLatch是没有办法的。
比如,有10000个计算线程,一个输出线程在wait,输出线程只能等10000个计算线程都计算完,wait才会结束,然后才能输出。只有一次输出。

也许你想在计算的线程里面把目前被同步的sum给实时打印出来,这样每个计算线程计算结束之后就把当时的实时的结果打印出来,那么计算线程就不符合单一责任原则了。

对于目前已经完成多少个任务,再加个线程共享的变量(任务完成计数器)来自增也不好。
就算用上AtomicInteger也是一种负担。

所以我不觉得应该使用CountDownLatch

线程同步synchronized是不需要的,用AtomicInteger会快一些。

CyclicBarrier就更不需要了,我没有写代码测试,但是按照我的理解,如果你开启11个线程,10个线程在计算,1个线程等待输出,只有当所有线程都执行到await()才能继续。
如果你开的线程就上千个,那么且不是上千个线程都在那边等待?

我还是推荐使用ExecutorCompleteionService

1. ExecutorCompleteionService可以随时拿到完成计算的线程的结果,然后在这个基础上继续进行加运算,而且不需要创建一个在线程之间共享的变量,每个线程有自己的结果变量,这个变量最终会作为task的结果之一返回。
2. 可以随时知道完成的计算线程的计数,当然需要ExecutorCompleteionService执行够快才行,如果计算线程执行很快就结束的话,完成的线程数会不准

我没有在机子上试
贴个Java核心技术的例子代码
ExecutorCompleteionService service = new ExecutorCompleteionService(executor);
for(Callable<Integer> task : tasks) {
    service.submit(task);
}
for(int i = 0; i < tasks.size(); i++) {
    count += service.take().get();// 如果有线程返回,马上可以得到结果,如果没有任何线程返回,阻塞等待结果
}
这里不需要sum这个所有的线程都共享的变量。因此也不需要同步或者AtomicInteger。



lanxianzhi说的不错,那段代码却是存在这些问题。在这个场景中CyclicBarrier不合适,他应该用在更灵活的控制中。

TO linchao198401。ExecutorCompleteionService我试了,他的优越之处在于实时性,而invokeAll()方法要等待所有的任务全部完成或者超时时才返回。
0 请登录后投票
   发表时间:2010-07-16  
实现原理非常清除,顶!
0 请登录后投票
论坛首页 Java企业应用版

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