JMM

Java memory model is divided into stack and heap, stack space is unique to each thread, each thread in the stack space will have multiple method frame, see the following code,


public static void main(String[] args) {
   Object a = new Object();
}

Copy the code

When this code runs, the JVM starts a thread called main, allocates stack space for that thread, and runs a method stack frame called main.

And when this main method runs, there will be a local private variable of type Object called A in the stack frame,

New Object() this Object will be created on the heap, and objects in the heap can be used elsewhere, but local private variables in the frame can be accessed only by the heap itself,

For example, in the code below, test1 and test2 methods both have a local variable called I, and their state values are isolated from each other, but they can access the same object, A.

Object a = new Object();

public void test1() {
    int i = 0;
    System.out.println(a);
}

public void test2() {
    int i = 1;
    System.out.println(a);
}
Copy the code

In the Java memory model, everything is shareable except local variables in methods that are thread private, and data state sharing raises a number of issues, such as security, correctness, and so on.

The Java Memory Model is defined as jSR-133: Java Memory Model and Thread Specification.

A copy of the

Although public variables can be shared, in order to exploit the power of multi-core CPU threads, public variables also have copies in each thread.

Because the thread and synchronize data between memory overhead is very large, CPU and its own cache access to data, than to take data of memory much faster, each thread on its own will have a copy of the working memory, on a regular basis and main memory (which is really have this data memory, is the heap) to the state of the data synchronization,

Since each thread holds a copy of data in its own memory, when multiple threads modify their local copy and then synchronize it to the main memory, inaccurate copy data will occur, resulting in the final write back data is also wrong.

Look at the code below:

static boolean cancel = false; public static void main(String[] args) { new Thread(() -> { while (! Cancel) {} system.out.println (" I'm done "); }).start(); cancel = true; }Copy the code

The author’s running results are as follows:

In an endless loop, but this situation does not happen every time I run, the author of this loop is running the program several times,

That is, some of it will end up normal, some of it will end up dead, so you can test it out, run it a few times,

Why is that? This is caused by the copy just mentioned, since the loop is in a new thread and a public variable, cancel the loop if it is false,

We set cancel = false on the main thread, which means another thread cances the loop, but this doesn’t work every time,

Sometimes an infinite loop doesn’t happen, sometimes an infinite loop happens precisely because the main thread and newThread both have copies of this Cancel,

Sometimes the main thread changes its local copy to false, but the value of the copy is not written to main memory in time, or the newThread does not synchronize the value from main memory to its own copy in time.

As a result, the two cancel values are different, and the running status of the program cannot achieve the desired effect. How to solve this problem?

volatile

We can use the valatile keyword to modify the variable to ensure that the problem caused by the copy of the variable,

This keyword has the following characteristics:

  1. Volatile variables that, when written, are written directly to main memory

  2. Volatile variables that, when read, are read directly from main memory

So using this keyword, both threads read and write the variable using the main memory instead of the local copy.

So you can quickly see the changes made by the other party and make the correct response. The changes are as follows:

static volatile boolean cancel = false; public static void main(String[] args) { new Thread(() -> { while (! Cancel) {} system.out.println (" I'm done "); }).start(); cancel = true; }Copy the code

It is important to note that volatile guarantees only visibility, that is, immediate response to changes, and does not guarantee atomicity. In other words, when multiple threads modify the state of a variable, they do not guarantee that the final result is correct.

Instruction rearrangement

Instruction rearrangement, where the compiler and CPU adjust the sequence of seemingly unrelated code during compilation or execution to achieve better performance,

This means that your code may be executed in a different order than you wrote it in. If this is the case, it may cause problems in some special cases. See the following code:

static boolean initFinished = false; public static void main(String[] args) { init(); initFinished = true; New Thread(() -> {while (true) {if (initFinished) {new Thread(() -> {while (true) {if (initFinished) { } } }).start(); } public static void init () {// some initialization operations}Copy the code

In the above code, we start with the initialization method, and after the initialization method is done, we set initFinished to true,

Then another thread listens for initFinished to be true and does some resource cleaning,

However, due to instruction rearrangement, it is likely that our two lines above will be processed as:

by

   init();
   initFinished = true;
Copy the code

Arrange for

   initFinished = true;
   init();
Copy the code

So the CPU or the compiler thinks that our two lines of code are unrelated, and they’re interchanged to optimize execution efficiency,

When initFinished is true, the init method hasn’t been executed yet, and another thread listens for initFinished to be true, thinking that init is finished,

Do some post-cleanup, but init isn’t really done yet, so that’s the inconvenience of reordering instructions,

The solution to this problem is to use the volatile keyword to modify initFinished, which means that volatile can prevent instruction reordering,

static volatile boolean initFinished = false; . init(); InitFinished = true; // Create a memory barrier after writing.......Copy the code

A volatile variable that creates a memory barrier to prevent instruction reordering before it is read from memory or after it is written.

So the benefits of using volatile are the immediate visibility of variables and the inconvenience of reordering programs by preventing instruction reordering.

There is no need for volatile when synchronization is available

When a variable is treated with synchronized, Lock, or AtomicInteger, volatile is no longer necessary.

Because synchronization is more rigorous, atomicity can be guaranteed in addition to visibility and rearrangement issues.