论坛首页 Java企业应用论坛

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

浏览 94803 次
该帖已经被评为良好帖
作者 正文
   发表时间:2010-07-16  
经过测试我发现我写的这个实现还不如java ArrayList 直接遍历相加求和快呢。
数据量大了更是如此吧。
import java.util.ArrayList;
import java.util.Random;


public class countBasic {

    public static void main(String[]a){

      int index = 100000;
      ArrayList<Integer> numbers = new ArrayList<Integer>();

      ArrayList<RunnableBasic> obj = new ArrayList<RunnableBasic>();
      System.out.println("开始生成数组"+System.currentTimeMillis());
        for(int i=0;i<1000*100*10;i++){
          Random rd = new Random();
          int t = rd.nextInt();
          numbers.add(t>0?t:0-t);      
         }

        System.out.println("生成完毕"+System.currentTimeMillis());

        ArrayList<Integer> arr = new ArrayList<Integer>();

        for(int j =0;j<numbers.size();j++){

           arr.add(numbers.get(j));

           if((j %  index== 0 && j != 0 )|| (j == numbers.size()-1)){
              
              RunnableBasic rb = new RunnableBasic();

              rb.vt = new smallBasic();

              rb.Numbers = arr;

              obj.add(rb);

              arr = new ArrayList<Integer>();

           }

        }

        ArrayList<Thread> th  = new ArrayList<Thread>();
        System.out.println("分组完毕"+System.currentTimeMillis());
        for(int k = 0 ; k<obj.size();k++){
            new Thread(obj.get(k)).start();
        }
       
        boolean end = false;

        while(!end){
            int m = 0;
            for(int q=0;q<obj.size();q++){
                if(obj.get(q).end.equals("end")){
                    m++;
                }
            }
            if(m == obj.size()){end = true;}
        }

        Sumcenter s = Sumcenter.getInistance();

        int sum = 0;

        for(int u=0;u<s.get().size();u++){
           sum += s.get().get(u);
        }

        System.out.println("和是:"+sum);
        System.out.println("计算完毕"+System.currentTimeMillis());
        int au = 0;
        for(int aj =0;aj<numbers.size();aj++){
            // System.out.println(numbers.get(aj));
            au += numbers.get(aj);
        }
        System.out.println("和是:"+au);
        System.out.println(System.currentTimeMillis());
    }


}


import java.util.ArrayList;

public class Sumcenter {
   
    public ArrayList<Integer> Brain;

    private static Sumcenter c;
   
    private Sumcenter(){
        Brain = new ArrayList<Integer>();
    }

    public static Sumcenter getInistance(){
       if(c == null)
           c = new Sumcenter();
        return c;
    }

    public void add(int i){
        Brain.add(i);
    }

    public ArrayList<Integer> get(){
        return Brain;
    }
}

public class smallBasic {
   
    private int sum=0;

    public void add(int i){
      this.sum += i; 
    }

    public int getSum(){
        return this.sum;
    }
   
}

public class RunnableBasic implements Runnable {

    protected smallBasic vt;
   
    protected ArrayList<Integer> Numbers;

    protected String end ="";

    public void run() {
        for(int i=0;i<Numbers.size();i++){
           vt.add(Numbers.get(i));
        }
        Sumcenter c = Sumcenter.getInistance();
        c.add(vt.getSum());
        end = "end";
    }
   
}

开始生成数组1279266504921
生成完毕1279266505734
分组完毕1279266505843
和是:-1912057435
计算完毕1279266505875
和是:-1912057435
1279266505890

直接遍历相加所用时间:15
分组遍历多线程在求和所用时间:141
希望大家看看我写的代码哪里出问题了。
0 请登录后投票
   发表时间:2010-07-16  
如果能把整个list放入内存里面,当然是ArrayList内部相加快了。

假设有18个CPU在电脑上面,你直接相加能用到几个CPU?
只有用到一个,因为只有一个线程在运行。那么其他17个CPU就浪费了。

如果分成18个线程相加速度会不会快点。

你写了好多的类,看来看去很复杂。
调用这么多的方法,栈的使用也是很消耗资源的。
不知道你创建了多少个线程。线程也是要资源。所以才导致速度慢。

就说这个代码:
        for(int u=0;u<s.get().size();u++){
           sum += s.get().get(u);
        }
Coding style不好
s我也不知道是什么意思。
s.get()我也不知道返回什么,原来是里面的一个ArrayList。用get做方法名还是很难理解,不能换个名字吗?
然后每次进行u<s.get().size()尽管ArrayList的size()可以马上返回数值,但是你为什么不把size()的值拿到循环外面呢?因为那个值是固定的,没有必要每次比较的时候都去调用一次函数。不知道JIT会不会对你的代码进行了优化,因为那个函数已经访问恩多次的。
然后循环的计数用u?大家都用i j k m等等,没有见过有人用u的。
s.get().get(u)这个也是,相信过1个月之后,你自己看代码也不知道到底拿到的是什么东西了。

我在想为什么你没有同步的代码,原来你是不断的轮询。轮询是很费CPU的时间的。而且你这里面也没有Thread.sleep,这个线程会不断的抢其他线程的计算时间。

    while(!end){
            int m = 0;
            for(int q=0;q<obj.size();q++){
                if(obj.get(q).end.equals("end")){
                    m++;
                }
            }
            if(m == obj.size()){end = true;}
        }

计算开多线程,只要内存够多,尽管多线程之间切换需要一些资源和时间,但是也没有15到141的差距的。
0 请登录后投票
   发表时间:2010-07-16  
hardPass 写道
kakaluyi 写道
mercyblitz 写道
kakaluyi 写道
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;
		}
		[color=red]Thread.currentThread().join();[/color]		System.out.println("和为:" + MyWorkThread.getSum());
	}
}

52行Thread.currentThread().join();朋友有个地方不懂,这里Thread.currentThread()是主线程吧,那join()方法就是是主线程等待主线程执行完成,这不是抛出InterruptedException吗


不会,只是会活锁。你可以在main方法里面试试。

经过验证,不是活锁,证明了我的担心,是个死锁

public static void main(String args[])
{
try {
Thread.currentThread().join();
System.out.println("ok");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ok永远打印不出来,主线程等待所有子线程结束的方法是不是酱紫地,前面一个同学说了一个方法
atominit方法才是正解


Thread.currentThread().join(); 莫非是传说中的永久sleep,必须等待别人来打断?




它并没有Sleep,而是工作完成之后,在等待。它不能被唤起,传说中的永久sleep应该是Thread#sleep(Long.MAX_VALUE)。
0 请登录后投票
   发表时间:2010-07-16   最后修改:2010-07-16
我的思路:有一个处理数值相加的类:smallBasic 每个线程会有单独的smallBasic。
线程类RunnableBasic来处理smallBasic的值。Sumcenter来存放每个线程里smallBasic相加的值。最后把每个线程运行求和的结果相加就是整个集合的值。
  while(!end){
            int m = 0;
            for(int q=0;q<obj.size();q++){
                if(obj.get(q).end.equals("end")){
                    m++;
                }
            }
            if(m == obj.size()){end = true;}
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace(); 
            }
        }
这段代码我是想等待所用的线程都运行完、然后在去处理每个线程相加的结果。
也不知道还有没有更合适的判断方法。

代码烂、所以时间才会差好多。
0 请登录后投票
   发表时间:2010-07-16  
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();
		}













join和wait是两码事。join不存在线程之间的唤醒,比如Object#notify操作。并且notify和wait操作,必须在Thread的同步下。而join的意义是wait to die。

join还是有办法打断的,在Thread运行时,报出一个异常就可以把当前线程搞死。


比如:
		Thread t =new Thread (new Runnable(){
			public void run(){
				throw new RuntimeException("On purpose!");
			}
		});
		
		t.start();
		
		try {
			t.join();
		} catch (InterruptedException e) {

		}
		System.out.println("+++");

0 请登录后投票
   发表时间:2010-07-16  
kakaluyi 写道
mercyblitz 写道
kakaluyi 写道
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;
		}
		[color=red]Thread.currentThread().join();[/color]		System.out.println("和为:" + MyWorkThread.getSum());
	}
}

52行Thread.currentThread().join();朋友有个地方不懂,这里Thread.currentThread()是主线程吧,那join()方法就是是主线程等待主线程执行完成,这不是抛出InterruptedException吗


不会,只是会活锁。你可以在main方法里面试试。

经过验证,不是活锁,证明了我的担心,是个死锁

public static void main(String args[])
{
try {
Thread.currentThread().join();
System.out.println("ok");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ok永远打印不出来,主线程等待所有子线程结束的方法是不是酱紫地,前面一个同学说了一个方法
atominit方法才是正解


在Java中,死锁应该是指两个线程在同步互斥中,由于释放锁的顺序不当,造成相互等待。而活锁是指,一个线程等待另外一个线程的答复。在这里,Main Thread等待自身(Main Thread)消亡而造成的永久等待,并没有在同步互斥下。因此,我理解为活锁。
0 请登录后投票
   发表时间:2010-07-16   最后修改:2010-07-16
我尝试了一下。
确实是会导致无限期的等待。
join()方法不应该等待当前的线程,而应该等待其他线程的执行的结束,当然被等待的那个线程必须要可以结束的,不能是while(true)

我也不知道这应该就死锁还是活锁。或者根本就跟锁没有关系。
只是线程的无限期的等待而已。没有任何的同步块或者资源在里面。

跟while(true) {

}
是差不多的。

我用了jvisualvm工具之后发现
Thread.currentThread().join();比while的方式还好一点。
就是Thread.currentThread().join();是线程等待,不会有CPU的时间片的执行。
而while是不断的执行时间片。

join的用法参考搜索到的文章
http://deepfuture.iteye.com/blog/599684
0 请登录后投票
   发表时间:2010-07-16  
linchao198401 写道

join()方法不应该等待当前的线程,而应该等待其他线程的执行的结束,当然等待的那个线程必须要可以结束的,不能是
http://deepfuture.iteye.com/blog/599684


你的说法不对,如果是等待其他线程执行结束,那么设计API时候,完全可以把join方法设计成静态方法。

Javadoc上面说了:

引用
Waits for this thread to die.


This thread 就是指本Thread实例。

0 请登录后投票
   发表时间:2010-07-16  
请看看我给的join的例子的链接,看看里面是等谁die。

然后javadoc说的是this thread die。而不是说current thread die.
this是通过对象调用的。
当你调用的是thatThread.join(),那么你等的就是thatThread的die。

已经试过currentThread().join()是不会完成就已经知道自己等自己die是不行的了。
0 请登录后投票
   发表时间:2010-07-16  
linchao198401 写道
请看看我给的join的例子的链接,看看里面是等谁die。

然后javadoc说的是this thread die。而不是说current thread die.
this是通过对象调用的。
当你调用的是thatThread.join(),那么你等的就是thatThread的die。

已经试过currentThread().join()是不会完成就已经知道自己等自己die是不行的了。



嗯,貌似Thread#currentThread()方法,返回是当前线程,也就是执行Thread#currentThread()语句的方法的线程。

线程有父子关系,你看下Thread#init()方法(被被构造器其调用)。当一个新的线程在start方法调用之前,Thread#currentThread是其父线程,在start方法之中,再调用Thread#currentThread则是其对象本身。

父线程执行完之后,子线程不一定启动。你给的资源中,子线程join之后,该子线程必须等待到die,而这时main方法中,Thread#currentThread()是Main Thread,这个时候调用Thread#currentThread().join()会发生无限期等待。但是,比如在一个方法中,在Thread#currentThread()的join和其他Thread实例start,如果不存在父子关系的话,那么不一定其他Thread实例在调用start方法后也不一定会执行。不知道你是否能够想到这种场景。

总之,Thread对象调用join()方法仅仅让自身等待到die,和其他的线程执行没有关系。
0 请登录后投票
论坛首页 Java企业应用版

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