DEV Community

A N M Bazlur Rahman
A N M Bazlur Rahman

Posted on • Originally published at bazlur.com on

Java Thread Programming: Lesson 3

PhotoPhoto by Wonderlane on Unsplash

Previous articles :

  1. Java Thread Programming: Lesson 1
  2. Java Thread Programming: Lesson 2

When we write a program, we test its correctness. If the program works according to our expectations, it builds confidence. If the same program runs in a multithread environment and produces the correct result, we call that piece of code thread-safe. Thread safety is critical; otherwise, we will end up having the wrong output, which is not desirable. In this article, we will discuss another problem towards ensuring thread safety.

In the previous article, we have discussed race condition. However, that is not the only problem we deal with when we work with java threads. Let us see an example-

package com.bazlur;

public class Day021 {
    static boolean running = true;

    public static void main(String[] args) {
        var t1 = new Thread(() -> {
            while (running) {
            }
            System.out.println("from the other side");
        });

        var t2 = new Thread(() -> {
            running = false;
            System.out.println("Hello");
        });
        t1.start();
        t2.start();
    }
}

Enter fullscreen mode Exit fullscreen mode

Look at the above code, it used two threads, and they share data, which is a Boolean, running.

Note that the first thread has a while loop, and this loop will keep running until the variable running is true. After the loop, we have a print statement that prints a text. In the second thread, the first statement changes the running variable from true to false, and then it prints another text.

If you look at the program, at first sight, we would think it will print the following –

Hello
from the other side.

Enter fullscreen mode Exit fullscreen mode

Nevertheless, in reality, it will not be the only case. It will produce a different result in different machines, even in the same machine, if we run repeatedly. Let me explain why-

In a single-threaded environment, the execution order of the code remains the same as the way it is written. However, it does not remain the same in a multithreaded environment. It depends on the thread scheduler, the processor and the interaction between two threads.

There are three possible outputs of this program-

  1. In the first thread, the while loop will keep running. Until then, the following line will not be executed. However, when the second thread starts, the first thing it does is, change the value of the running variable. It breaks the while loop of the first thread, thus, it prints the “Hello.” Meanwhile, the second thread prints the “from the other side.”
Hello
from the other side

Enter fullscreen mode Exit fullscreen mode
  1. The loop inside the first thread will keep running, and the second thread will change the value and print the text, “from the other side,” and then the loop of the first thread will break and print the “Hello”.
from the other side
Hello      

Enter fullscreen mode Exit fullscreen mode
  1. Most of us can imagine this far, but there is another possibility that is also trickiest. The first thread will keep running the loop forever, and although the second thread will change the value of running, making it false and print the text, the first thread will not see the changes. In such a case, the program’s output will be only- “from the other side.”

Now in which case it could happen that the first thread will not read the changes? Usually, we have multiple processors in our modern computer. Each processor has some caches along with them. They are called L1, L2, and L2 cache. The processor uses these caches to execute the program faster without going to the main memory each time. It might well happen that the two threads in the above program can run in two different processors, and each of them can cache the variable, running inside their L caches. If the second thread changes the value, the first thread will not see it because it keeps reading it from the caches. This is called a visibility problem. Although the value was updated, it was not visible to the other thread. This often called data race.

L Caches

Now the question is, how do we fix it?

If we could prevent the thread from not reading from the caches, the fix is to read them constantly from the main memory. There is a keyword for that in java, which is volatile.

The above code will fix if we add this keyword before running.

package com.bazlur;

public class Day021 {
    static volatile boolean running = true;

    public static void main(String[] args) {
        var t1 = new Thread(() -> {
            while (running) {
            }
            System.out.println("from the other side");
        });

        var t2 = new Thread(() -> {
            running = false;
            System.out.println("Hello");
        });
        t1.start();
        t2.start();
    }
}

Enter fullscreen mode Exit fullscreen mode

The above code will always produce the correct result.

Reading from and writing to cache is always cheap compare to the main memory. Thus, we should only use a volatile variable when we need to enforce the visibility of variables.

That is for today! Cheers.

Top comments (0)