线程:忙等待 - 空的while循环

6
在大学的课程中,我们了解了Threads并以等待TrafficLightCar的“忙等待”方法为例。为此,我们构建了三个类:
  • TrafficLight(实现Runnable)
  • Car(实现Runnable)
  • Main
在我们的Main类中,启动两个线程,一个是Car,另一个是TrafficLightCar具有布尔属性hasToWait。该类中的run()方法按照如下方式工作:只要hasToWait == true,它就会通过while循环工作。为了改变这一点,我们在Car类中使用notifyCar()方法,该方法由TrafficLight使用。 TrafficLight中的run()方法通过Thread.sleep()运行来模拟等待的时间。
在我的教授那里一切顺利,但最终我遇到了严重的问题。只要Car类中的while循环为空,我就有问题。当我放入一个非空的System.out.println()时,它可以工作。但是,如果Syso为空,则结果是不显示Run方法的文本。当TrafficLight中的Thread.sleep()0时,它也可以与空的while循环一起使用。
以下是我的代码:
package trafficlight;

public class Car implements Runnable {

    private boolean hasToWait = true;

    public void run() {
        this.crossTrafficLight();
    }

    public void crossTrafficLight() {
        while(hasToWait){ for(int i = 0; i<20; i++){System.out.println("123");}} // Busy waiting
        System.out.println("Auto fährt über Ampel");
    }

    public void notifyCar() {
        this.hasToWait = false;
        System.out.println("Test");
    }
}

TrafficLight.java:

package trafficlight;

public class TrafficLight implements Runnable {
    private Car car;

    public TrafficLight(Car car) {
        this.car = car;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.car.notifyCar();
    }
}

Main.java:

package trafficlight;

public class Main {

    public static void main(String[] args){
        Car car = new Car();
        TrafficLight tl = new TrafficLight(car);

        new Thread(car).start();
        new Thread(tl).start();
    }

}

问题出在哪里?为什么它在我的教授电脑上可以运行,但是在我的电脑上不行?我已经在Eclipse Juno中使用了1:1的代码,使用了JRE 1.7


考虑使用线程 join 方法。 - Mr. P
嘿,我有类似的问题,但是还没有完全理解原因;( - parsecer
2个回答

6
除了在此答案中提到的所有内容(将该答案中的finished替换为您的hasToWait),添加println后代码开始工作的原因如下:
  • println是一个同步的方法;
  • 您在两个线程中都调用它;
  • 这创建了两个线程之间的先行发生关系;
  • 因此,对布尔标志的写入变得对子线程可见。

你可以说它开始工作大多是偶然的:你在println中利用了正在进行的同步。


非常感谢,这很清楚地解释了为什么它与“Syso”一起工作。他并没有教错我们, 我们的任务之一是查找实现可能发生的问题,但我仍然困惑为什么它在他的计算机上的工作方式与在我的计算机上不同。 - Kaibear

1
您的代码中真正的问题是实例字段hasToWait。该字段被两个线程使用,汽车线程读取该值,交通灯线程在一段时间后更新该值。 以某种方式同步访问此字段是必要的。 有两种方法可以做到这一点:
  1. 使用synchronized关键字。可以通过在读取或写入的所有位置使用同步块,或者更好地编写同步的getter和setter,然后在Car类中使用getter和setter来实现。

  2. 使用volatile关键字。只需将字段声明为volatile即可。该关键字正是为了这种情况而存在的。有关volatile的更多信息可以在Oracle's Java Tutorials中找到。

阅读有关原子访问的文章(请参见上面的链接)后,应清楚地了解选项2(声明volatile)是更好的选项 - 对于此用例而言。
现在,你看到你的电脑和教授的电脑之间的区别:只要你使用单核处理器,你将会像同步一样在其他线程上看到实例字段的更新,因为CPU不必在其他内核缓存区域中同步这些值。如果你使用多核处理器,那么JVM能够在多个内核上运行线程。这意味着,这些内核必须同步值,而volatile机制正是为此而设计的。

1
最后一段是一系列错误的陈述。写入的可见性根本不仅仅与CPU缓存有关。运行时允许将 while (!flag) {stuff} 编译成等效的 if (!flag) while (true) {stuff}。结果的影响将对CPU核心数量不敏感。 - Marko Topolnik
你说得对,当涉及到Java内存模型的内部机制时,还有许多其他要考虑的主题(也在这里)。我应该说,我的解释是观察到的行为的一个可能原因。至少,这是我的经验! - Seelenvirtuose
你可能只是经历了相关性,而没有证明因果关系。即使条件稍微改变一点,也足以在单核上引发问题。 - Marko Topolnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接