[Kill Thread Part.2-1] Java memory model - underlying principles

[Kill Thread Part.2-1] Java memory model - underlying principles

1. What is the "underlying principle"? What does this chapter study?

1. From Java code to CPU instructions

  • From. java file to. The class file is then translated by the JVM into machine instructions corresponding to the operating system platform.
  • JVM implementations will bring different "translations". Machine instructions of different CPU platforms are so different that the concurrent security effect is not guaranteed to be consistent.
  • Focus begins to shift down: specifications and principles for the transformation process

2. JVM Memory Structure VS Java Memory Model VS Java Object Model

1. Overall orientation

  • JVM memory structure: related to the runtime region of the Java virtual machine
  • Java memory model: related to concurrent Java programming
  • Java object model: related to how Java objects are represented in virtual machines

(2) JVM memory structure

(3) Java object model

Java is an object-oriented language, and each Java object is stored in a JVM with a certain structure.

  • Storage model of Java object itself

  • The JVM creates an instance Klass for this class, which is saved in the method area to display the Java class at the JVM level.

  • When we create an object in Java code using new, the JVM creates an instance OopDesc object that contains the object header and the instance data.

(4) What is the Jav memory model?

Detailed description below

2. What is JMM (Java Memory Model)

  • Is Specification
    • There is no concept of a memory model in C
      • Depending on the processor, different processors produce different results
      • Concurrency security cannot be guaranteed
    • A standard is required for predictable results from multithreaded runs
    • Is a set of specifications that require the implementation of each JVM to comply with the JMM specifications so that developers can use them to develop multithreaded programs more easily.
      • If there is no such memory model to regulate, it is likely that the reordering of different rules in the JVM will result in different results on different virtual machines, causing a lot of problems.
  • Is the principle of tool classes and keywords
    • The principles of volatile, synchronized, Lock, etc. are JMM
    • Without JMM, it would be cumbersome to specify when to use the memory fence and so on. Fortunately, with JMM, we can develop concurrent programs with only synchronization tools and keywords.
  • The three most important things: reordering, visibility, atomicity

3. Reordering

1. Case demo: What is reordering?

(1) Test code

/**
 * Description: Demonstrates the phenomenon of reordering "stop until a condition is reached", testing for small probability events
 */
public class OutOfOrderExecution {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread one = new Thread(new Runnable() {
            @Override
            public void run() {
                a = 1;
                x = b;
            }
        });
        Thread two = new Thread(new Runnable() {
            @Override
            public void run() {
                b = 1;
                y = a;
            }
        });
        one.start();
        two.start();
        one.join();
        two.join();
        System.out.println("x = " + x + "," + "y = " + y);
    }
}

Run result:

(2) Reordering analysis

The execution order of these four lines of code determines the final x and y results in three cases:

  • Although the order of code execution may vary, it is within thread 1, that is, a = 1; x = b;

    • The order in which the amounts of these two lines of code are executed is unchanged, that is, a = 1, which is executed before x = b; Similarly, thread 2's b = 1 executes before y = a.
  • The odd (0, 0) scenario appears

    • Test code:

      • /**
         * Description: Demonstrates the phenomenon of reordering "stop until a condition is reached", testing for small probability events
         */
        public class OutOfOrderExecution {
        
            private static int x = 0, y = 0;
            private static int a = 0, b = 0;
        
            public static void main(String[] args) throws InterruptedException {
                int i = 0;
                for (; ; ) {
                    i++;
                    x = 0;
                    y = 0;
                    a = 0;
                    b = 0;
        
                    CountDownLatch latch = new CountDownLatch(3);
        
                    Thread one = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                latch.countDown();
                                latch.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            a = 1;
                            x = b;
                        }
                    });
                    Thread two = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                latch.countDown();
                                latch.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            b = 1;
                            y = a;
                        }
                    });
                    two.start();
                    one.start();
                    latch.countDown();
                    one.join();
                    two.join();
        
                    String result = "No." + i + "Secondary (" + x + "," + y + ")";
                    if (x == 0 && y == 0) {
                        System.out.println(result);
                        break;
                    } else {
                        System.out.println(result);
                    }
                }
            }
        }
        

(3) Why does it happen (0, 0)?

Will x = 0,y =0 occur? That's because reordering occurs, one of the possibilities for executing four lines of code.

What is reordering:

The actual execution order of the two lines of code inside thread 1 is inconsistent with the order of the code in the Java file. Code instructions are not executed strictly in the order of code statements. Their order has been changed, which is reordering, where the inverted y = a and b = 1 lines of statements.

2. Benefits of reordering: faster processing

(1) Instructions before reordering

(2) reordered instructions

You can see that fewer instructions are available, which makes execution faster.

3. Optimization of reordering

Compiler optimization: including JVM, JIT compiler, etc.

CPU Instruction Rearrangement: The CPU may rearrange instructions even if the compiler does not rearrange them

Memory "reordering": Thread A's modified thread B is invisible, causing visibility problems (memory and cache problems)

4. Visibility

1. Case demo: What is visibility problem

(1) Test code

/**
 * Description: Problems with demo visibility
 */
public class FieldVisibility {

    int a = 1;
    int b = 2;

    private void change() {
        a = 3;
        b = a;
    }


    private void print() {
        System.out.println("b=" + b + ";a=" + a);
    }

    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }
    }
}

(2) Possible results

These three scenarios can be caused by a multithreaded execution sequence.

However, there is another case where memory visibility issues are the cause.

3. Detailed analysis

The two threads cannot communicate directly, only by accessing the main memory. One write, one read.

2. Solve problems with volatile

If volatile is prefixed to the values of a and B in the example code above, there will be no visibility problem.

principle

3. Why are there visibility problems?

  • CPU has a multi-level cache, causing read data to expire
    • Cache size is smaller than memory, but it is only slower than registers, so there are more Cache layers between CPU and main memory
    • Visibility issues with shared variables between threads are not directly caused by multicores, but by multicaches.
    • If all cores use only one cache, then there will be no memory visibility issues.
    • Normally, each core reads its own data into an exclusive cache, and when the data is modified, it also writes to the cache, then waits to be flushed into main memory. This causes some cores to read values that are out of date.

4. JMM abstraction: main memory and local memory

Java, as a high-level language, masks these underlying details and uses JMM to define a set of specifications for reading and writing memory data. Although we no longer need to worry about first-level and second-level caching, JMM abstracts the concepts of main memory and local memory.

Local memory is not really a block of memory allocated to each thread, but an abstraction of JMM, from registers, first-level caches, second-level caches, and so on.

The relationship between main memory and local memory

JMM has the following provisions:

  • All variables are stored in main memory, and each thread has its own working memory, where the contents of the variables are copies of the main memory.
  • Threads cannot read and write variables directly in main memory. Instead, they can only operate on variables in their own working memory and then synchronize to main memory.
  • Main memory is shared by multiple threads, but working memory is not shared between threads, and if communication is required between threads, it must be done using in-main memory transit.

All shared variables exist in memory, each thread has its own local memory, and threads read and write shared data are swapped through local memory, which causes visibility problems.

V. Happens-Before Principle

1. What is happens-before

  • The happens-before rule is used to solve visibility problems:
    • In time, action A occurs before action B, and B guarantees to see A, which is happens-before.
  • Two operations can be used happens-before to determine their order of execution:
    • If one operation happens-before is in another operation, then we say that the first operation is visible to the second operation

2. What is not happens-before

  • There is no mechanism for the two threads to work together, so the execution results of code X and Y are not guaranteed to be always seen by each other, so there is no happens-before.

3. What are the principles?

  • Single Thread Rule
    • If it is single-threaded, subsequent code must see the operation of the previous code.
      • Because each thread has its own working memory
      • However, reordering is possible.
  • Lock operation
      • Assuming A gets the lock, releases the lock after executing the ynchronized code block, and B gets the lock, you can see all of A's actions.
  • volatile variable
    • As long as it is written, the read thread must be able to read volatile-modified variables.
  • Thread Start
  • Thread join
    • Subthread join, the statement below the main thread join can see all the operations of the join's subthread.
  • Transitivity
    • If hb(A, B) and hb(B, C), then hb(A, C) can be introduced
  • interrupt
    • A thread is interrupted by another thread, so detect interrupt isInterrupted or throw InterruptedException must see
  • Construction method
    • Last line instruction of object construction method happedns-before and first line instruction of finalize() method
  • happens-before principle for tool classes
    • Thread-safe container get must be able to see put and other save actions before re-entering
    • CountDownLatch
    • Semaphore
    • Future
    • Thread Pool
    • CyclicBarrier

Added by mingo on Fri, 28 Jan 2022 01:07:29 +0200