Threads: Busy Waiting - Empty While-Loop [duplicate]
Asked Answered
I

2

6

During our lessons in the university, we learned about Threads and used the "Busy Waiting" method for an example of a Car waiting at a TrafficLight. For this task we build three classes:

  • TrafficLight (implements Runnable)
  • Car (implements Runnable)
  • Main

In our Main class we start two Threads, one of Car, and one of TrafficLight. The Car has the boolean attribute hasToWait. The run() method in this class works the way, that it works through a while loop as long as hasToWait == true. To change this, we have the notifyCar() method in the Car class, which is used by the TrafficLight. The run() method in TrafficLight runs through a Thread.sleep() to simulate a certain time of waiting.

Everything works fine at my Prof's but eventually I have serious problems with it. As long as the while loop in the Car class is empty. When I put in a System.out.println() - which is not empty, it works. But if the Syso is empty, the result is no displaying of the Text of the Run method. Also it's working when the Thread.sleep() in TrafficLight is 0. Than it works with an empty while loop.

Here is my code:

Car.java:

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();
    }

}

Where is the problem? Why does it work at my profs but not at my computer? I got the code 1:1 in my Eclipse Juno, using JRE 1.7

Isborne answered 23/1, 2014 at 11:8 Comment(2)
Consider using thread join method.Rodge
Hey I have similar problem but didn't quite get the reason ;(Nitin
A
6

In addition to everything said in this other answer (just substitute your hasToWait for finished in that answer), the reason why the code starts working when you add a println is as follows:

  • println is a synchronized method;
  • you call it in both threads;
  • this creates a happens-before relationship between the two threads;
  • therefore the write to the boolean flag becomes visible to the child thread.

You could say that it starts working mostly by accident: you are piggybacking on the synchronization going on in println.

Apfelstadt answered 23/1, 2014 at 11:17 Comment(1)
Well thank you, this quite explains why does it work with "Syso". He is not teaching us wrong at all, one of our tasks was the looking up of possible incidents of the implementation, but still I was confused why it did not work the same way as it does on his computer.Isborne
M
1

The real problem with your code is the instance field hasToWait. This field is being used by two threads. The car thread reads the value, and the traffic light thread updates the value after some time.

The access to this field must be synchronized in some way.

There are two ways to do this:

  1. Use the synchronized keyword. Either by using a synchronized block at all places, where it is read or written, or - better - write a synchronized getter and a synchronized setter, then use the getter and the setter inside the Car class.

  2. Use the volatile keyword. Just declare your field as volatile. This keyword exists for exactly that case. More information on volatile can be found in Oracle's Java Tutorials.

After reading the article about atomic access (see link above), it should be clear that option 2 (declaring volatile) is the far better option - for this use case.

Now to the difference you see between your computer and your professor's computer: As long as you are using a single-core-processor, you will see updates on an instance field in other threads as though they were synchronized, because the CPU does not have to synchronize these values in the other cores' cache areas. If you use a multi-core-processor, then the JVM is able to run threads on several cores. That means, that these cores have to synchronize values, and the volatile mechanism is exactly designed for that.

Mesotron answered 23/1, 2014 at 13:5 Comment(3)
The last paragraph is a series of false statements. Visibility of writes is not at all just about CPU caches. The runtime is allowed to compile while (!flag) {stuff} into the equivalent of if (!flag) while (true) {stuff}. The resulting effect will be insensitive to the number of CPU cores.Apfelstadt
You are right, and there are many more topics to consider (also here) when heading into the Java Memory Model internals. I should have said, that my explanation is a possible reason for the observed behavior. At least, this is what I experienced!Mesotron
You probably experienced just correlation without having proven causation. Even a slight modification of conditions would have beeen enough to provoke the problem on single core.Apfelstadt

© 2022 - 2024 — McMap. All rights reserved.