基本 Java 线程(4 个线程)比非线程慢
Basic Java threading (4 threads) slower than non-threading
我有一个四核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 毫秒。如果我这样做,如果我还在非线程 class 上调用 Thread.sleep(50),线程 运行 会更快。
太棒了,我知道如何让它发挥作用。但是我学习这个的原因是我正在做寻路,我不会在已经需要很长时间并且不需要暂停并且甚至 1ms 什么都不做的东西上添加睡眠调用(除非它绝对必须)。
所以,我想知道我错过了什么?线程是否绝对必须进入睡眠状态,或者对象是否必须等待它们才能按我的意图工作(即 运行 并行执行所有四个循环)?
即使我只是犯了一个错误,为什么要花这么长时间?我认为最坏的情况,它仍然会在 11 秒内 运行,它会以某种不可预见的顺序完成....
执行时间的巨大差异是由Math.random()
方法造成的。如果您深入研究它的实现,您会发现它使用在所有线程之间共享的静态 randomNumberGenerator
。如果你更深入一步,那么你会注意到执行依赖于 int next(int)
方法,而该方法又使用 Random.seed
,即 AtomicLong
(考虑到所有线程都使用相同的 Random
!)。现在我们来到 AtomicLong
,这是通过 optimistic locking 实现的——这就是问题所在。乐观锁不是为高负载设计的,当多个线程试图同时访问它们时,它们会受到很大影响,这就是您观察到的性能下降。
TL;DR:使用 ThreadLocalRandom(感谢@bayou.io 提到这一点)并享受性能提升。
你的问题是你正在使用 Math.random()
。有关此方法的文档:
...
This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.
(强调我的)
所以解决方案是为每个线程创建一个新的Random
。
我有一个四核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 毫秒。如果我这样做,如果我还在非线程 class 上调用 Thread.sleep(50),线程 运行 会更快。
太棒了,我知道如何让它发挥作用。但是我学习这个的原因是我正在做寻路,我不会在已经需要很长时间并且不需要暂停并且甚至 1ms 什么都不做的东西上添加睡眠调用(除非它绝对必须)。
所以,我想知道我错过了什么?线程是否绝对必须进入睡眠状态,或者对象是否必须等待它们才能按我的意图工作(即 运行 并行执行所有四个循环)?
即使我只是犯了一个错误,为什么要花这么长时间?我认为最坏的情况,它仍然会在 11 秒内 运行,它会以某种不可预见的顺序完成....
执行时间的巨大差异是由Math.random()
方法造成的。如果您深入研究它的实现,您会发现它使用在所有线程之间共享的静态 randomNumberGenerator
。如果你更深入一步,那么你会注意到执行依赖于 int next(int)
方法,而该方法又使用 Random.seed
,即 AtomicLong
(考虑到所有线程都使用相同的 Random
!)。现在我们来到 AtomicLong
,这是通过 optimistic locking 实现的——这就是问题所在。乐观锁不是为高负载设计的,当多个线程试图同时访问它们时,它们会受到很大影响,这就是您观察到的性能下降。
TL;DR:使用 ThreadLocalRandom(感谢@bayou.io 提到这一点)并享受性能提升。
你的问题是你正在使用 Math.random()
。有关此方法的文档:
...
This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.
(强调我的)
所以解决方案是为每个线程创建一个新的Random
。