The role of the volatile keyword

A key role

  • Keep memory visible: All threads can see the latest state of shared memory.
  • Prevent instruction reordering: Ensure that the order of code changes to machine instructions remains the same.
  • Ensure atomicity for certain types of operations, such as,longanddouble, but not guaranteed exclusivity (can be operated by multiple threads simultaneously (in parallel)),
    • doubleIt’s 64-bit, and the CPU, in the case of 32-bit,doubleWill be split into two 32 bits for operation and storage. In concurrent pairsdoubleIt is possible that the two 32-bit bits of a double are being operated on by different threads, resulting in data inconsistency, plusvolatileKeyword can guarantee the rightdoubleAtomicity of operations

That is, volatile guarantees three major features of concurrent programming

  • Visibility: When multiple threads access the same variable and one thread changes the value of the variable, other threads can immediately see the changed value. That is, one or more operations as a whole, either all or none of them are executed, and the operations are not interrupted by the thread scheduling mechanism during execution. And once this operation starts, it runs through to the end without any context switch.
  • Orderliness: that is, the order in which the program is executed is the order in which the code is executed.
  • Atomicity (part): That is, one or more operations as a whole are either all or none executed without being interrupted by the thread scheduling mechanism. And once this operation starts, it runs through to the end without any context switch.
    • Partial atomicity:volatile int a=b+1;There is no guaranteea=b+1Student: Atomicity of theta becausebMay be manipulated by multiple threads, resulting inaThe results are inconsistent,Even if thebHave also beenvolatileModification. If you have to usea=b+1Please ensure that it can only be used by one thread. It may not mean much, but it can be guaranteedaVisibility of variables
    • That is if you want to implementvolatileThe atomicity of the write operation, then in the assignment variable on the right hand side of the equals signCan’tPresent a variable shared by multiple threads,Even if theThis variable is also treated byvolatileNor can embellishment.So the best thing isvolatile int a=1;
    • volatile Date date=new Date();.There’s no guaranteeThe atomic

General principles of the volatile keyword

The required knowledge

Java code layer Java bytecode layer C++ source code layer CPU layer read volatile variables write volatile variables

Visibility sample

public class volatileT {
    public static volatile int found=0;
    public static void change(a){
        found=1;
    }
    public static void main(String[] args) {
        Runnable wait = ()->{
            System.out.println("Waiting for a pen from a gay friend.");
            while (0==found){// Found ==0 on the loop, waiting for gay friends thread will find =1
            }
            System.out.println("Here comes the pen. Start writing.");
        };
        Runnable give = ()->{
            try{
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("My gay friend found a pen. Send it over...");
            change();// Gay friend thread will find =1
        };
        new Thread(wait,"I thread").start();
        new Thread(give,"Gay Friend Thread").start(); }}Copy the code

Found is volatile, causing all threads of found to be visible. When my gay friend thread will find =1, my thread will see found=1, and the while loop is broken and the program ends. If the found modifier is not volatile, the program will not end because my thread cannot see found=1 in my gay friend’s thread, and the while loop persists

How does volatile ensure that threads are visible

  • Access to volatile variables is not locked and therefore does not block the thread of execution, making volatile variables a lighter synchronization mechanism than the sychronized keyword. When reading or writing non-volatile variables, each line first copies the variables from memory to the CPU cache. If the computer has multiple cpus, each thread may be processed on a different CPU, which means that each thread can be copied to a different CPU cache. Declared variables are volatile, and the JVM ensures that each read is read from memory, skipping the CPU cache and reading directly from memory.

Data in the Cache is stored in Cache lines.

  • Multi-core cpus have multiple caches. If there are shared variables, the cache Consistency Protocol (MESI) is introduced to ensure that the CPU is consistent with the data in each core (thread) cache.
  • Why did Java introduce volatile when CPUS had cache consistency protocols
    • Volatile shields the hardware layer
    • Triggering Conformance Protocol (MESI)
    • Volatile uses the conformance protocol (MESI) to implement its keywords

To explain a visibility implementation in a small way, you have to jump from the application layer to the hardware layer. Programmers simply want to do multithreaded programming. In order to do not want to understand the hardware layer (ignore the implementation of the underlying hardware), want to better multithreading programming, and in order to solve the multithreading, may appear problems, such as data consistency, correctness, security. In addition, the common Java developers, compilers and JVM engineers can reach a consensus, on the basis of the hardware abstraction of a protocol — Java Memory Model (JMM), to ignore the hardware, cross-platform implementation

JMM (Java Memory Model)

The significance of the JMM

  • The Java memory model describes multithreaded codeWhat is legalAnd how threads interact through memory. It describes the relationships between variables in a program and the low-level details of storing them in and retrieving them from memory or registers in a real computer system. It is implemented in such a way that the correct implementation can be optimized using various hardware and various compilers. Java contains multiple language keywords, includingVolatile, finalsynchronized, designed to help the programmer to the compilerdescribeThe concurrency requirements of the program. Java memory modelDefines the volatile synchronizedbehaviorAnd more importantly, ensure that the Java program is properly synchronized inRunning correctly on all processor architectures.
  • Memory barriers are only indirectly related to the high-level concepts described in the memory model, such as “(acquire) acquisition” and “(release) release”. The memory barrier itself is not a “synchronization barrier.” And memory barriers are independent of the kind of “write barriers” used in some garbage collectors. The memory barrier instructions only directly control the INTERACTION between the CPU and its cache,
  • The results of multithreading can be explained and predicted by the JMM
  • The Java memory model is a specification proposed for the consistency of the Java language’s cross-platform performance, shielding hardware and operating system implementation. For example, it specifies the abstract relationship between threads and main memory. Since it is a specification, it only specifies the concept, and the specific implementation depends on the implementation of JVM virtual machines on different platforms.

The figure above is the JMM, according to its interpretation of visibility

  • Each time a volatile modified shared variable is read from main memory, a copy is made in working memory

  • Further, this is done by adding memory barriers and disallowing instruction reordering optimizations.

  • A write to a volatile variable is followed by a store barrier instruction. The ability to write the latest data to the (working memory) cache back to main memory, ensuring that the data written is immediately visible to other threads.

  • A read on a volatile variable is followed by a load barrier instruction. Data in working memory (cache) can be invalidated and reloaded from main memory to ensure that the latest data is read.

  • In plain English, a volatile variable forces a reread of its value from main memory each time it is accessed by a thread, and when the variable changes, forces the thread to flush its latest value to main memory, so that different threads can always see its latest value at any time.

  • A thread writes a volatile variable:

    • Change the value of a volatile copy in the thread working memory.
    • Flushes the value of the changed copy from working memory to main memory.
  • A thread reads a volatile variable:

    • Reads the latest value of a volatile variable from main memory into the thread’s working memory.
    • Reads a copy of the volatile variable from working memory.

Note: The JVM memory model and the JMM Java memory model are not the same concept

  • The latter is the concept of multi-threaded use

Instruction rearrangement

  • Compile – time instruction rearrangement
    • There is no rearrangement at the Java bytecode level
    • The openjdk, also known as intermediate code (c++), rearranges instructions when compiling GCC
  • Run-time instruction rearrangement
    • The CPU rearranges instructions
      • Follow the semantics and rules of the code
      • As-if-serial (semantic)
        • Whatever the order, the result must be correct, in the single thread case, right
      • Happens-before (rule)
        • Some code execution order is built into the JVM in advance. For example, new and Finalize () method objects can only be destroyed when they are created first. Instructions cannot be randomly rearranged
        • A happens before B, B happens before C, => A happens before C

Start with a DCL creating a double check lock

Double check

Interview question: Should DCL singletons be volatile? A: to

public class SingleDCLTest {// Double check singleton mode
    private static volatile SingleDCLTest INSTANCE;
    int i= 13;
    public SingleDCLTest(a){}public static SingleDCLTest getInstance(a){
        if (INSTANCE ==null) {// Check again
            synchronized (SingleDCLTest.class){/ / lock
                   if (INSTANCE ==null) {// Double check
                       INSTANCE = newSingleDCLTest(); }}}return INSTANCE;
    }

    public static void main(String[] args) {
        Runnable DCLTest = ()-> System.out.println(SingleDCLTest.getInstance().hashCode());
        IntStream.range(0.100).forEach(i -> new Thread(DCLTest).start());//100 threads trying to create a singleton and output hashCode}}Copy the code

New is not an atomic operation

1, request memory (zero value), the object inside the member variable is 0 or the initial value 2, initialization method (constructor)(zero value -> given value) 3, object reference point (generation).

  • In theory (it seems impossible to measure in code)
    • Steps 2 and 3, when executed on the CPU, may be executed 3 and then 2 — instructions are rearranged, after all, in multithreaded mode, the results are the same
    • In DCL singleton mode, if there are multiple threads, the situation may be wrong: when a thread instruction is reordered, step 3 is executed before Step 2. When the reference to the object is generated before step 2 has started, it may be immediately fetched from the heap by another thread, which creates the singleton directlyreturn INSTANCE;, that is returnedA semi-initialized object, inside the member variable has not been assigned, will cause the program result is incorrect (null pointer exception);
    • Therefore, it is necessary to forbid the reordering of instructions to prevent similar situations from happening
  • The keywordvolatilePrevents command reordering

Instructions: Instructions that are sent to a computer processor.

How to ensure order

  • Disallow command reordering

Disallow instruction rearrangement through the memory barrier

  • Memory barriers are introduced to make it easier for programmers to control the execution order because some logical orders are unpredictable by the JVM
  • Solve run-time instruction reordering problems
  • volatileThe keyword is usedLockInstructions,LockPrefix,LockIt’s not a memory barrier,But it can do something like a memory barrier.
  • volatileThe keyword is not a memory barrier but performs a memory barrier function, preventing instructions on both sides of the barrier from being reordered
  • The lock prefix instruction acts as a memory barrier (also known as a memory barrier) that provides three main functions:
    • Ensure that instruction reordering does not place subsequent instructions in front of the memory barrier, nor does it place previous instructions behind the memory barrier; That is, by the time the memory barrier instruction is executed, all operations in front of it have been completed;

  • Force changes to the cache to be written to main storage immediately, using the cache consistency mechanism, and the cache consistency mechanism will prevent the simultaneous modification of the memory region data cached by more than two cpus; (Visibility reasons)
  • If it is a write operation, it invalidates the corresponding cache line in the other CPU.

reference

  • The JSR-133 Cookbook for Compiler Writers
  • Why JMM
  • Probably the most popular Java memory model in the Eastern hemisphere
  • Volatile and memory barriers