java multithreaded practical learning visibility

Preface

Recently, I've been watching java Concurrent practice, which has benefited a lot, but I feel that I have a feeling of swallowing the whole thing. After work, I don't have the system in school. First, I don't have teachers to teach me. Second, I don't have schedule planning. Third, I don't have a good eye and a low hand.
I plan to write this series of microblogs. The first goal is to supervise my own learning. The second goal is to share experiences and lessons with you.

visibility

Visibility is a problem introduced by the memory mechanism of the jvm. Time and space are both contradictory topics. In order to improve efficiency, there is a direct synchronization process between each thread's memory and main memory.
For details, please refer to java memory distribution

Let's look at the examples in the book

public class NoVisibility {
    private static boolean ready;
    private static int number;

    public static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println("number:" + number);
        }
    }

    public static void main(String[] args) {
        ReaderThread readerThread = new ReaderThread();
        readerThread.start();
      
        number = 42;
        ready = true;
    }
}

As described in the book, there may be a dead cycle, and the number may be printed with a value of 0.
However, only number:42 will be printed all the time, which is the only result of all attempts.
But unfortunately, I have tried for a long time, including creating thread tests, but it still hasn't been reproduced.
Is it the book or the way we tried it wrong.

One or two thread speed problems

When the main thread finishes running, the sub thread starts. As a result, the value read by the sub thread every time has changed. The modification code is as follows:

public class NoVisibilitySleep {
    private static boolean ready;
    private static int number;

    public static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println("number:" + number);
        }
    }

    public static void main(String[] args) {
        ReaderThread readerThread = new ReaderThread();
        readerThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        number = 42;
        ready = true;
    }

}

The result is the same, number:42

The role of Thread.yield

Remove Thread.yield

public class NoVisibilitySleepWithoutYield {
    private static boolean ready;
    private static int number;

    public static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
//                Thread.yield();
            }
            System.out.println("number:" + number);
        }
    }

    public static void main(String[] args) {
        ReaderThread readerThread = new ReaderThread();
        readerThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        number = 42;
        ready = true;
    }

}

At this time, something magical happened. This program is always a dead cycle. (when the two conditions are met at the same time, there will be a dead cycle.)

However, if you use the debug mode to break the point in the while position, or add System.out.println in the loop, there will be no dead loop in general.
In theory, printing shouldn't change the program, but this strange phenomenon appears.
There are three articles on this issue:

  1. R is greater than knowledge R explanation
  2. Articles on csdn Interesting talk visibility
  3. Visibility understanding
    Most of the opinions in Articles 1 and 3 are the same. The reason for the dead loop is that if there is a space in while, or number + +, the compiler will optimize the code to
 int localReady = ready
  while (!localready) {
   }

localReady can only be assigned once, so it will lead to a dead cycle. The System.out.println compiler is not optimized, so the result will be output.
Visibility and code optimization are different expressions of the same thing. Because of the jvm's own memory structure, the code will be optimized accordingly. Visibility, copy of main memory and thread memory is the representation of the underlying data, and code optimization is the specific means of implementation.
So, the book says it may never be visible, which is the case.

But why Thread.yield and Thread.sleep appear in the loop? The program can run directly. There is no accurate answer. Officially, it won't affect, but it does in reality.

summary

At present, I can't reproduce the examples in the book. I haven't found out the reason. I need to inquire more information to get the reason.
The conclusion is that for this kind of variable in flag bit form, only atomic operations such as set and get are generally available. By introducing the keyword volatile, the code can not be optimized and the elements of main memory can be read at any time.
But volatile only guarantees visibility, not atomicity. Next time we're looking at atomicity

Published 47 original articles, won praise 11, visited 70000+
Private letter follow

Tags: Java jvm

Posted on Sun, 08 Mar 2020 04:12:53 -0700 by nysmenu