Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java multithreading - joining a CPU heavy thread and volatile keyword

So, follwoing some job interviews I wanted to write a small program to check that i++ really is non-atomic in java, and that one should, in practice,add some locking to protect it. Turns out you should, but this is not the question here.

So I wrote this program here just to check it.

The thing is, it hangs. It seems that the main thread is stuck on on t1.join() line, even though both worker threads should finish because of the stop = true from previous line.

I found that the hanging stops if :

  • I add some printing inside the worker threads (as in the comments), probably causing the worker threads to sometime give up CPU or
  • If I mark the flag boolean stop as volatile, causing the write to immediately be seen by worker threads, or
  • If I mark the counter tas volatile... for this I have no idea what causes the un-hanging.

Can someone explain what's going on? why do I see the hang and why does it stop in those three cases?

public class Test {   

    static /* volatile */ long t = 0;
    static long[] counters = new long[2]; 
    static /* volatile */ boolean stop = false;

    static Object o = new Object();
    public static void main(String[] args) 
    {
        Thread t1 = createThread(0);
        Thread t2 = createThread(1);

        t1.start();
        t2.start();

        Thread.sleep(1000);

        stop = true;

        t1.join();
        t2.join();

        System.out.println("counter : " + t + " counters : " + counters[0] + ", " + counters[1]  + " (sum  : " + (counters[0] + counters[1]) + ")");

    }

    private static Thread createThread(final int i)
    {
        Thread thread = new Thread() { 
            public void run() {
                while (!stop)
                {
//                  synchronized (o) {                      
                        t++;
//                  }

//                  if (counters[i] % 1000000 == 0)
//                  {
//                      System.out.println(i + ")" + counters[i]); 
//                  }
                    counters[i]++;
                }
            }; 
        };
        return thread;
    }
}
like image 864
Yossi Vainshtein Avatar asked Apr 03 '17 07:04

Yossi Vainshtein


People also ask

How is volatile keyword used in multithreading?

Volatile keyword is used to modify the value of a variable by different threads. It is also used to make classes thread safe. It means that multiple threads can use a method and instance of the classes at the same time without any problem. The volatile keyword can be used either with primitive type or objects.

How volatile will work if we have multiple threads writing to a volatile variable?

If you write volatile variable from multiple threads without using any synchronized constructs, you are bound to get data inconsistency errors.

Is volatile keyword thread safe?

Therefore, the volatile keyword does not provide thread safety when non-atomic operations or composite operations are performed on shared variables. Operations like increment and decrement are composite operations.


2 Answers

Those might be the possible reasons :

  • Marking "stop" as volatile prevents its value being cached in the worker threads states (e.g registers)
  • Marking "t" as volatile makes sure that updated value is read when "t" is accessed. However, this behavior might also get "stop"'s updated value if JVM arranged those variables near each other, so they might be read and stored in the same "cache line". Try adding @Contended annotation to see if behavior persists in this case as well .You may find additional information on : https://en.wikipedia.org/wiki/False_sharing , https://mechanical-sympathy.blogspot.am/2011/07/false-sharing.html , https://blogs.oracle.com/dave/entry/java_contented_annotation_to_help
  • Calling the "System.out.println()" actually performs a system call hence making a transition to the "native calls stack" which probably also makes the "processor cache" to purge. What does a JVM have to do when calling a native method?

If you are interested in digging deeper into the topic then I would suggest to go over "Java Concurrency in Practice by Brian Goetz" book.

like image 44
Ruben Avatar answered Oct 14 '22 22:10

Ruben


It seems that the main thread is stuck on on t1.join() line, even though both worker threads should finish because of the stop = true from previous line.

In the absence of volatile, locking, or other safe publication mechanism, the JVM has no obligation to ever make stop = true visible to other threads. Specifically applied to your case, while your main thread sleeps for one second, the JIT compiler optimizes your while (!stop) hot loop into the equivalent of

if (!stop) {
    while (true) {
        ...
    }
}

This particular optimization is known as "hoisting" of the read action out of the loop.

I found that the hanging stops if :

  • I add some printing inside the worker threads (as in the comments), probably causing the worker threads to sometime give up CPU

No, it's because PrintStream::println is a synchronized method. All known JVMs will emit a memory fence at the CPU level to ensure the semantics of an "acquire" action (in this case, lock acquisition), and this will force a reload of the stop variable. This is not required by specification, just an implementation choice.

  • If I mark the flag boolean stop as volatile, causing the write to immediately be seen by worker threads

The specification actually has no wall clock-time requirements on when a volatile write must become visible to other threads, but in practice it is understood that it must become visible "very soon". So this change is the correct way to ensure that the write to stop is safely published to, and subsequently observed by, other threads reading it.

  • If I mark the counter t as volatile... for this I have no idea what causes the un-hanging.

These are again the indirect effects of what the JVM does to ensure the semantics of a volatile read, which is another kind of a "acquire" inter-thread action.

In summary, except for the change making stop a volatile variable, your program switches from hanging forever to completing due to the accidental side-effects of the underlying JVM implementation, which for simplicity does some more flushing/invalidation of thread-local state than required by the specification.

like image 156
Marko Topolnik Avatar answered Oct 14 '22 23:10

Marko Topolnik