Windows 中的竞争条件程序,但 Ubuntu 中没有
Program with race conditions in Windows, but not in Ubuntu
在一个旨在突出竞争条件的作业中,我们得到了以下代码
public class IncreaseDecrease {
public static int IntegerVariable = 0;
public static final int NUM_ITER = 5000000;
public static void main(String[] args) throws Exception {
Increase inc;
Decrease dec;
while (true) {
inc = new Increase();
dec = new Decrease();
inc.start();
dec.start();
inc.join();
dec.join();
System.out.println(IntegerVariable);
IntegerVariable = 0;
Thread.sleep(750);
}
}
}
class Increase extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.IntegerVariable++;
}
}
}
class Decrease extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.IntegerVariable--;
}
}
}
如果每个线程都可以在另一个线程读取它之前更新该值,则此代码应该打印 0,但由于竞争条件不会发生这种情况,它可以打印 -5000000 到 5000000 之间的任何值。
我 运行 windows 和 repl.it 上的代码,它给出了预期的输出:
-310951
-1918567
-3374495
-3219135
-2286639
-3221055
-3794319
-2442047
-2776415
-3617391
但是在Ubuntu上,当我运行它的时候,它每次都给0。
我的问题是,为什么会这样? Ubuntu 管理线程的方式是否不同,或者它只是我计算机的特例?
编辑:
在将增量放入不同的方法并向其添加一个操作后,我观察到了竞争条件。这是最终代码:
public class IncreaseDecrease {
public static int IntegerVariable = 0;
public static final int NUM_ITER = 5000000;
public static void main(String[] args) throws Exception {
Increase inc;
Decrease dec;
while (true) {
inc = new Increase();
dec = new Decrease();
inc.start();
dec.start();
inc.join();
dec.join();
System.out.println(IntegerVariable);
IntegerVariable = 0;
Thread.sleep(750);
}
}
public static void increment ()
{
IntegerVariable++;
double a = Math.pow(3, 7);
}
public static void decrement()
{
IntegerVariable--;
double a = Math.pow(3, 7);
}
}
class Increase extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.increment();
}
}
}
class Decrease extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.decrement();
}
}
}
在 Java 中存在一个关于线程的常见误解,认为它们真正有效地均匀交错处理。这实际上并不完全正确,不同系统上的不同 JVM 工作方式不同。
这一切都源于JVM 决定切换线程。 JVM 可能会在遇到 Thread.sleep()
或阻塞方法(例如 synchronized
或 lock
时切换线程,但通常如果线程不执行任何涉及阻塞等的操作,它会让线程 运行.
你的循环在递增和递减一个值时不停地旋转。如果您在循环中添加 Thread.sleep(0)
调用,您可能会看到不同之处,因为您为 JVM 提供了更多切换线程的机会 out/in。
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.IntegerVariable--;
// Add this.
Thread.sleep(0);
}
I'd go out on a limb and claim that Hotspot under Linux using the server compiler while it doesn't on Windows is the more likely explanation: The compiler can replace the whole loop with a single expression which is something that HotSpot is definitely capable of. Adding any native method will make that impossible thereby making it much more likely to observe the race condition
我猜也可能是这种情况。
您是否尝试过让您的 IntegerVariable 易变?这将防止可能发生的一些编译器优化。
public static volatile int IntegerVariable = 0;
在一个旨在突出竞争条件的作业中,我们得到了以下代码
public class IncreaseDecrease {
public static int IntegerVariable = 0;
public static final int NUM_ITER = 5000000;
public static void main(String[] args) throws Exception {
Increase inc;
Decrease dec;
while (true) {
inc = new Increase();
dec = new Decrease();
inc.start();
dec.start();
inc.join();
dec.join();
System.out.println(IntegerVariable);
IntegerVariable = 0;
Thread.sleep(750);
}
}
}
class Increase extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.IntegerVariable++;
}
}
}
class Decrease extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.IntegerVariable--;
}
}
}
如果每个线程都可以在另一个线程读取它之前更新该值,则此代码应该打印 0,但由于竞争条件不会发生这种情况,它可以打印 -5000000 到 5000000 之间的任何值。
我 运行 windows 和 repl.it 上的代码,它给出了预期的输出:
-310951
-1918567
-3374495
-3219135
-2286639
-3221055
-3794319
-2442047
-2776415
-3617391
但是在Ubuntu上,当我运行它的时候,它每次都给0。
我的问题是,为什么会这样? Ubuntu 管理线程的方式是否不同,或者它只是我计算机的特例?
编辑: 在将增量放入不同的方法并向其添加一个操作后,我观察到了竞争条件。这是最终代码:
public class IncreaseDecrease {
public static int IntegerVariable = 0;
public static final int NUM_ITER = 5000000;
public static void main(String[] args) throws Exception {
Increase inc;
Decrease dec;
while (true) {
inc = new Increase();
dec = new Decrease();
inc.start();
dec.start();
inc.join();
dec.join();
System.out.println(IntegerVariable);
IntegerVariable = 0;
Thread.sleep(750);
}
}
public static void increment ()
{
IntegerVariable++;
double a = Math.pow(3, 7);
}
public static void decrement()
{
IntegerVariable--;
double a = Math.pow(3, 7);
}
}
class Increase extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.increment();
}
}
}
class Decrease extends Thread {
@Override
public void run() {
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.decrement();
}
}
}
在 Java 中存在一个关于线程的常见误解,认为它们真正有效地均匀交错处理。这实际上并不完全正确,不同系统上的不同 JVM 工作方式不同。
这一切都源于JVM 决定切换线程。 JVM 可能会在遇到 Thread.sleep()
或阻塞方法(例如 synchronized
或 lock
时切换线程,但通常如果线程不执行任何涉及阻塞等的操作,它会让线程 运行.
你的循环在递增和递减一个值时不停地旋转。如果您在循环中添加 Thread.sleep(0)
调用,您可能会看到不同之处,因为您为 JVM 提供了更多切换线程的机会 out/in。
for (int i = 0; i < IncreaseDecrease.NUM_ITER; i++) {
IncreaseDecrease.IntegerVariable--;
// Add this.
Thread.sleep(0);
}
I'd go out on a limb and claim that Hotspot under Linux using the server compiler while it doesn't on Windows is the more likely explanation: The compiler can replace the whole loop with a single expression which is something that HotSpot is definitely capable of. Adding any native method will make that impossible thereby making it much more likely to observe the race condition
我猜也可能是这种情况。
您是否尝试过让您的 IntegerVariable 易变?这将防止可能发生的一些编译器优化。
public static volatile int IntegerVariable = 0;