【问题标题】:Basic Java threading (4 threads) slower than non-threading基本 Java 线程(4 个线程)比非线程慢
【发布时间】:2015-08-22 08:35:26
【问题描述】:

我有一个四核 CPU。我创建了 4 个线程并运行了一个 cpu 密集型循环,它比在一个线程中以程序方式运行它所花费的时间 > 4 倍。

我创建了两个项目进行比较,一个有线程,一个没有。我将展示代码和运行时间。请注意没有线程的项目看起来很奇怪的原因是我想复制内存开销,因为我不确定它会对运行时间产生多大影响。所以,这里是没有线程的代码:

class TimeTest implements Runnable {
    private Thread t;
    private String name;

    TimeTest(String name) {
        this.name = name;
        System.out.println("Creating class " + name);
    }

    public void run() {
        System.out.println("Running class " + name);

        int value = 100000000;
//        try {
            while (--value > 0) {
                Math.random();
//                Thread.sleep(1);
//                System.out.println("Class " + name + " " + value);
            }
//        } catch (InterruptedException e) {
//            System.out.println("Interrupted " + name);
//        }

        System.out.println("Class " + name + " exiting...");
    }

    public void start() {
        System.out.println("Starting class " + name);

        if (t == null) {
            t = new Thread(this, name);
//            t.start();
            this.run();
        }
    }
}

public class ThreadComp {
    public static void main(String[] args) {
        TimeTest one = new TimeTest("Class-1");
        one.start();

        TimeTest two = new TimeTest("Class-2");
        two.start();

        TimeTest three = new TimeTest("Class-3");
        three.start();

        TimeTest four = new TimeTest("Class-4");
        four.start();
    }
}

这将在大约 11 秒内运行。

这是带有线程的代码:

class RunnableTest implements Runnable {
    private Thread t;
    private String name;

    RunnableTest(String name) {
        this.name = name;
        System.out.println("Creating thread " + name);
    }

    public void run() {
        System.out.println("Running thread " + name);
        int value = 100000000;
//        try {
            while (--value > 0) {
                Math.random();
//                Thread.sleep(1);
//                System.out.println("Thread " + name + " " + value);
            }
//        } catch (InterruptedException e) {
//            System.out.println("Interrupted " + name);
//        }

        System.out.println("Thread " + name + " exiting...");
    }

    public void start() {
        System.out.println("Starting thread " + name);

        if (t == null) {
            t = new Thread(this, name);
            t.start();
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        RunnableTest one = new RunnableTest("Thread-1");
        one.start();

        RunnableTest two = new RunnableTest("Thread-2");
        two.start();

        RunnableTest three = new RunnableTest("Thread-3");
        three.start();

        RunnableTest four = new RunnableTest("Thread-4");
        four.start();
    }    
}

这运行大约需要 1 分 13 秒。

现在,在我学习的示例中,他们在引导期间调用 Thread.sleep 50 毫秒。如果我这样做,如果我还在非线程类上调用 Thread.sleep(50),线程运行得更快。

这太棒了,我知道如何让它发挥作用。但是我学习这个的原因是我正在寻找路径,并且我不会在已经需要很长时间并且不需要暂停甚至 1ms 什么也不做的事情上添加睡眠调用(除非它绝对必须)。

所以,我想知道我错过了什么?线程是否绝对必须进入睡眠状态,或者对象是否必须等待它们才能按我的意图工作(即并行运行所有四个循环)?

即使我只是犯了一个错误,为什么要花这么长时间?我认为最坏的情况是,它仍然会在 11 秒内运行,它会以某种不可预见的顺序完成......

【问题讨论】:

  • 那么 - 4 个女性需要 36 个月才能生下一个孩子?
  • 我几乎可以保证Math.random的使用是这个问题的原因。见docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random--
  • ^^ @JAtkin 说的。您的代码基本上都是锁争用。
  • @bayou.io - 我想你的意思是问是否需要 9 个女性 1 个月才能生一个孩子......

标签: java multithreading concurrency parallel-processing


【解决方案1】:

执行时间的巨大差异是由Math.random()方法引起的。如果你深入研究它的实现,你会发现它使用了所有线程共享的静态randomNumberGenerator。如果您再深入一步,您会注意到执行依赖于int next(int) 方法,该方法又使用Random.seed,即AtomicLong(考虑所有线程都使用Random 的同一实例!)。现在我们来到AtomicLong,它是通过optimistic locking 实现的——这就是问题所在。乐观锁不是为高负载而设计的,当多个线程试图同时访问它们时,它们会受到很大的影响,这就是您观察到的性能下降。

TL;DR:使用ThreadLocalRandom(感谢@bayou.io 提到这一点)并享受性能提升。

【讨论】:

  • 做到了。如果没有线程,1999000000 nextFloat()s 需要 5 秒。使用线程 2(编辑:实际上 5 秒可能是侥幸)秒。事实上,这让我感到惊讶,因为我认为所有四个都需要 5 秒才能完成。但我会接受的。我的隐藏是我只选择了 Math.random() 因为我需要有足够开销的东西,但害怕打印到控制台会迫使它全部回到一个线程。无论如何,谢谢。
【解决方案2】:

您的问题是您正在使用Math.random()。此方法的文档:

...

此方法已正确同步以允许多个线程正确使用。 但是,如果许多线程需要快速生成伪随机数,则可能会减少每个线程争用自己的伪随机数生成器。

(强调我的)

所以解决方案是为每个线程创建一个新的Random

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2015-08-21
    • 1970-01-01
    相关资源
    最近更新 更多