Mind mapping

Star: github.com/yehongzhi/l…

Interviewer: What is JMM

I wouldn’t be so sleepy if you did that.

JMM is the Java Memory Model. Memory access varies between hardware manufacturers and operating systems, causing problems with the same code running on different systems. So the Java Memory Model (JMM) blocks out differences in memory access across hardware and operating systems to enable Java programs to achieve consistent concurrency across platforms.

The Java memory model specifies that all variables are stored in main memory, including instance variables, static variables, but not local variables and method parameters. Each thread has its own working memory. The working memory of the thread stores a copy of the variables used by the thread and the main memory. The operation of the variables is carried out in the working memory. Threads cannot read or write variables directly from main memory.

Different threads cannot access variables in each other’s working memory. The transfer of variable values between threads is done through main memory.

If that sounds abstract, I can draw a picture for you to visualize:

The working memory of each thread is independent, and the thread can only manipulate data in the working memory and then flush back to main memory. This is the basic way threads work as defined by the Java memory model.

Be warned, some people here will mistake the Java memory model for the Java memory structure, and then answer heap, stack, GC garbage collection, and end up with a very different question than the interviewer wants to ask. In fact, most questions about the Java memory model are about multi-threading, Java concurrency.

Interviewer: What does the JMM define

This simple, entire Java memory model is actually built around three characteristics. They are atomicity, visibility and order. These three characteristics are the foundation of Java concurrency.

atomic

Atomicity refers to the fact that an operation is indivisible and uninterruptible, and that one thread can execute it without interference from other threads.

The interviewer took a pen and wrote some code. Does the following code guarantee atomicity?

int i = 2;
int j = i;
i++;
i = i + 1;
Copy the code

The primitive type assignment operation must be atomic.

First read the value of I, then assign to j, two steps operation, cannot guarantee atomicity.

First read the value of I, then +1, and finally assign the value of I. There is no guarantee of atomicity.

The JMM guarantees only basic atomicity; to ensure atomicity for a block of code, it provides monitorenter and Moniterexit bytecode instructions, also known as the synchronized keyword. Thus, operations between synchronized blocks are atomic.

visibility

Visibility means that when one thread changes the value of a shared variable, other threads immediately know that the change has been made. Java uses the volatile keyword to provide visibility. When a variable is volatile, its modification is immediately flushed to main memory, and when other threads need to read the variable, the new value is read from main memory. Ordinary variables do not guarantee this.

In addition to the volatile keyword, final and synchronized are also visible.

The principle of synchronized is that shared variables must be synchronized into main memory before entering unlock.

Fields modified by final, once initialized, are visible to other threads if no object escapes.

order

In Java, you can use synchronized or volatile to ensure order between multiple threads. The implementation principle is somewhat different:

The volatile keyword is used to prevent instruction reordering by using a memory barrier.

The principle of synchronized is that a thread that locks must unlock before other threads can lock again, so that a block of code that is locked by synchronized is executed serially between multiple threads.

Interviewer: Tell me about eight memory interactions

Ok, interviewer, there are eight types of memory interaction. Let me draw a picture for you:

  • Lock, which acts on variables in main memory and identifies them as thread-exclusive.
  • Read, a variable acting on main memory, transfers the value of the variable from main memory to the thread’s working memory for the next load operation.
  • Load, a variable acting on working memory, puts a variable from main memory into a copy of the variable in working memory.
  • Use, a working memory variable, transfers the working memory variable to the execution engine. This operation is performed whenever the virtual machine reaches a bytecode instruction that requires the value of the variable to be used.
  • Assign, a variable applied to working memory that assigns a value received from the execution engine to a copy of the variable in working memory. This operation is performed whenever the virtual machine accesses a bytecode instruction that assigns a value to the variable.
  • Store, a variable that operates on working memory and passes a value from a variable in working memory to main memory for subsequent writes.
  • Write: A variable in main memory that puts the value of a variable in main memory from the store operation in working memory.
  • Unlock: A variable that acts on main memory. It releases a locked variable so that it can be locked by another thread.

Let me add the JMM rules for the eight types of memory interactions:

  • One of the read, load, store, and write operations is not allowed to appear alone. That is, the read operation must be loaded, and the store operation must be written.
  • A thread is not allowed to discard its recent assign operation, in which it must inform main memory that variable data in working memory has changed.
  • Threads are not allowed to synchronize unassigned data from working memory to main memory.
  • A new variable must be created in main memory. Working memory is not allowed to use an uninitialized variable directly. The load and assign operations must be performed before the use and store operations can be performed on variables.
  • Only one thread can lock a variable at a time. You can unlock the device only by executing the UNLOCK command for the same number of times.
  • If you lock a variable, it clears all of its values in working memory. Before the execution engine can use this variable, the value of the variable must be reloaded or assigned.
  • You cannot unlock a variable if it is not locked. You cannot unlock a variable that is locked by another thread.
  • Before a thread can unlock a variable, it must synchronize the variable back to main memory.

Interviewer: Talk about the keyword volatile

Heart: this can be a major drama, can not go wrong ~

The volatile keyword is used in many concurrent programs. It serves two main purposes:

  1. Ensure visibility of variables between threads.
  2. Disallow CPU instruction reorder.

visibility

Volatile modifies a variable that is immediately visible to other threads when one thread changes its value. Normal variables need to be re-read to get the latest value.

The process by which volatile guarantees visibility is roughly as follows:

Does volatile guarantee thread-safety

Conclusion first, volatile is not necessarily thread-safe.

To prove this, let’s look at the following code:

/ * * *@authorYe Hongzhi public account: Java technology enthusiast **/
public class VolatileTest extends Thread {

    private static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        Vector<Thread> threads = new Vector<>();
        for (int i = 0; i < 100; i++) {
            VolatileTest thread = new VolatileTest();
            threads.add(thread);
            thread.start();
        }
        // Wait for the child threads to complete
        for (Thread thread : threads) {
            thread.join();
        }
        // The correct result should be 1000, but it is 984
        System.out.println(count);/ / 984
    }

    @Override
    public void run(a) {
        for (int i = 0; i < 10; i++) {
            try {
                // Hibernate for 500 milliseconds
                Thread.sleep(500);
            } catch(Exception e) { e.printStackTrace(); } count++; }}}Copy the code

Why is volatile not thread-safe?

It’s very simple. Visibility doesn’t guarantee atomicity. Count ++ is not an atomicity operation. To ensure thread-safety, use the synchronized keyword or lock to lock count++ :

private static synchronized void add(a) {
    count++;
}
Copy the code

Disallow instruction reordering

The as-if-serial semantics, however reordered, cannot change the execution of a (single-threaded) program.

In order to make instructions more consistent with THE execution characteristics of CPU, maximize the performance of the machine and improve the execution efficiency of the program, as long as the final result of the program is equal to the result of its sequentialization, the execution order of the instructions can be inconsistent with the logical order of the code. This process is called instruction reordering.

There are three kinds of reordering, respectively: compiler reordering, instruction level parallel reordering, memory system reordering. The whole process is as follows:

Instruction reordering is fine in a single thread, does not affect execution results, and improves performance. However, in a multi-threaded environment, there is no guarantee that the results will not be affected.

Therefore, in a multithreaded environment, instruction reordering is prohibited.

The volatile keyword disallows instruction reordering in two ways:

  • When a program performs a read or write to a volatile variable, all changes to the preceding operation must have taken place, and the result must be visible to subsequent operations, and the subsequent operation must not have taken place.

  • During instruction optimization, statements that access volatile variables cannot be executed after them or statements that access volatile variables cannot be executed before them.

Here’s an example:

private static int a;// Nonvolatile modifies variables
private static int b;// Nonvolatile modifies variables
private static volatile int k;//volatile modifies variables

private void hello(a) {
    a = 1;  //语句1
    b = 2;  //语句2
    k = 3;  //语句3
    a = 4;  //语句4
    b = 5;  5 / / statement
    // The following omission...
}
Copy the code

The variables a and b are non-volatile, and k is volatile. Therefore, statement 3 cannot be placed before statements 1 or 2, nor after statements 4 or 5. But the order of statements 1 and 2 is not guaranteed, nor is the order of statements 4 and 5.

In addition, statement 1,2 must have completed by the time statement 3 is executed, and the result of statement 1,2 is visible to statements 3,4, and 5.

What is the mechanism by which volatile prohibits instruction reordering

Memory barriers can be divided into the following categories:

  • LoadLoad barrier: For statements such as Load1, LoadLoad, Load2. Ensure that the data to be read by Load1 is completed before the data to be read by Load2 and subsequent read operations is accessed.

  • StoreStore barrier: For Store1, StoreStore, Store2 statements, ensure that the write operation of Store1 is visible to other processors before Store2 and subsequent write operations are executed.

  • LoadStore barrier: For such statements Load1, LoadStore, Store2, ensure that the data Load1 needs to read is completed before Store2 and subsequent writes are flushed out.

  • StoreLoad barrier: For statements such as Store1, StoreLoad, Load2, write to Store1 is made visible to all processors before Load2 and all subsequent reads are executed.

A LoadLoad barrier is inserted after each volatile read and a LoadStore barrier is inserted after each read.

Each volatile write is preceded by a StoreStore barrier and followed by a SotreLoad barrier.

That’s basically how it works.

Interviewer: Not bad. Basically, I have covered everything. It’s getting late

conclusion

To learn concurrent programming, the Java Memory model is the first stop. Atomicity, orderliness, and visibility are the three basic features of concurrent programming. For the back to in-depth study to play a paving role.

In this article, the focus, if any, is on how the Java Memory Model (JMM) works, the three main features, and the volatile keyword. Why ask about the volatile keyword? Volatile can pull out a lot of things like visibility, order, memory barriers, and so on. It can be a good indicator of the skill level of the interviewer, after all, the interviewer also wants to efficiently screen out the qualified talent.

The code for all of the above examples is uploaded to Github:

Github.com/yehongzhi/m…

Please give me a thumbs-up if you think it is useful. Your thumbs-up is the biggest motivation for my creation

Refusing to be a salt fish, I’m a programmer trying to be remembered. See you next time!!

Ability is limited, if there is any mistake or improper place, please criticize and correct, study together!