为什么我的并发程序比顺序版慢?

时间:2011-08-13 18:15:34

标签: java multithreading

我正在尝试同时调用startjoin

    //Starting and Joining 
    for (Thread thread : threadArray) {
        thread.start();
        thread.join();
    }

首先与start进行比较,然后与join进行比较。

    //Starting Them
    for (Thread thread : threadArray) {
        thread.start();
    }
    //Joining Them
    for (Thread thread : threadArray) {
        thread.join();
    }

上述两种情况之间的性能差异是什么?

在第一个场景中,我几乎可以保证线程之间的执行顺序是顺序的。因此,如果我有n个线程,并说每个线程花费Ti时间来完成任务,那么我的总执行时间应该是Tis从1到n的总和。

在第二种情况下,我开始然后加入。这是我感到困惑的部分。难道时间不应该与上面几乎相同吗?我所看到的几乎是我机器的两倍。

我正在使用的整个代码示例如下。

public class ThreadJoin implements Runnable {

    public void run() {
        for (int i=0;i<10000000;i++) {
            //Random mathematical stuff independent of i.
             int ran = (int) (Math.random()*1000 -34)%47;
        }
    }

    public static void main(String[] args) throws Exception {
        Thread[] threadArray = new Thread[10];
        //Creating threads and feeding them with the job
        for (int i=0;i<10;i++) {
            threadArray[i] = new Thread(new ThreadJoin());
        }
        long currentTimeMillis = System.currentTimeMillis();
        System.out.println("Started at " + currentTimeMillis);
        //Starting Them
        for (Thread thread : threadArray) {
            thread.start();
        }
        //Joining Them
        for (Thread thread : threadArray) {
            thread.join();
        }
        long currentTimeMillis2 = System.currentTimeMillis();
        System.out.println("Ended at " + currentTimeMillis2);
        System.out.println("Diff : " +( currentTimeMillis2 - currentTimeMillis));
    }

}

3 个答案:

答案 0 :(得分:7)

理论上,首先启动所有线程然后加入它们都应该允许你的10个线程同时执行(即同时执行),而在一个循环中启动和连接线程将使它们并行运行。

因此,从理论上讲,双环变体应该更快。为什么它实际上更慢(如果我理解这一点)?

你在循环中使用Math.random()非常重要。事实上,我认为大部分工作都发生在这种方法中。 Math.random()是一个同步方法 - 这意味着一次只有一个线程可以执行它,而其他线程必须等到前一个完成。

所以,你不能真正比这里顺序更快。它实际上变慢了,因为你的许多线程之间有很多上下文切换,然后大多数线程会发现它们无法继续,因为另一个线程已经有锁。

为了使您的程序更快,让每个线程都有自己的java.util.Random()对象,并调用其nextRandom()方法。 (你可能想确保用不同的种子初始化它们。)

正如Tomek的评论中所提到的,从Java 7开始就有ThreadLocalRandom类,它为每个线程组织一个Random对象池,并通过其current()为当前线程公开一个方法。 (我从来没有使用过这个,所以与手动操作相比,我无法评论性能。)

答案 1 :(得分:5)

第二个版本(启动所有线程,然后加入所有线程)允许线程并行运行,因此它应该在多处理器或多核计算机上更快(更短的总时间)。但Math.random()是同步的,它可以使它实际上更慢。

从Math.random()的文档:

  

此方法已正确同步,以便更多地正确使用   比一个线程。但是,如果需要生成许多线程   伪随机数很快,它可能会减少争用   每个线程都有自己的伪随机数生成器。

答案 2 :(得分:3)

当你有一个没有做任何事情的循环时,服务器JIT可以检测到它消除它。第一次调用循环时,在检测到循环没有做任何事情之前会有一个小的延迟,但第二次调用它会快得多。

我的建议是,如果您关心启动和停止线程需要多长时间,则使用线程池。它几乎消除了这样做的需要,你不会比这更快。