The four classes of memory barrier instructions provided in the JVM

  • This barrier prevents the processor from reordering volatile reads from normal reads below
  • Storestore: Write. This barrier ensures that all common writes preceding volatile writes are flushed to main memory before volatile writes
  • Loadstore: Reads and writes. This barrier prevents the processor from reordering volatile reads above from normal writes below
  • Storeload: write read. This barrier prevents reordering of volatile and potentially volatile read/write operations

Volatile features

  • Guaranteed visibility
  • Disallow command reordering
  • Atomicity is not guaranteed

How does volatile guarantee visibility

Public class VolatileTest {/** * If volatile is used, the program stops after 1 second * if volatile is not used, the program does not stop */ static volatile Boolean flag = true; public static void main(String[] args) throws InterruptedException { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " start"); while (flag) { } System.out.println("over"); }, "t1").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { flag = false; }, "t2").start(); }}Copy the code

The eight atomic operations between working and main memory defined in the Java memory model

Graph LR read read --> Load --> use --> assign --> Store --> Write --> Lock --> unlock
  • Read: Applies to main memory, transferring the value of a variable from main memory to working memory, and from main memory to working memory
  • Load: Acts on working memory to place the variable values read transfers from main memory into a copy of the working memory variable, i.e., data load
  • Use: Applied to working memory, passing the value of a copy of a working memory variable to the execution engine whenever the JVM encounters a bytecode instruction that requires the variable
  • Assign: Applies to working memory and assigns a value received from the execution engine to the working memory variable. This operation is performed whenever the JVM encounters a bytecode instruction to assign a value to the variable
  • Store: Applied to working memory to write the assigned value of the working variable back to main memory
  • Write: applies to main memory and assigns the value of the variable transferred from store to the variable in main memory

Since the above can only guarantee the atomicity of a single instruction, there is no large area locking for the combination of multiple instructions, so the JVM provides two additional atomic instructions:

  • Lock: Used on main memory to mark a variable as a thread – exclusive state.
  • Unlock: Acts on main memory to release a locked variable before it can be occupied by another thread

How does volatile prohibit instruction reordering

    1. Volatile The act of prohibiting instruction reordering
    • When the first operation is a volatile read, there is no reordering, no matter what the second operation is; This operation ensures that operations following volatile reads are not reordered before volatile reads
    • When the second operation is volatile write, there is no reordering regardless of the first operation; This operation ensures that operations prior to volatile writes are not reordered after volatile writes
    • Reorder cannot be performed when the first operation is volatile write and the second operation is volatile read
    1. Four memory barriers inserted (see code analysis below)
    • Insert a StoreStore barrier before each volatile write
    • Insert a storeLoad barrier after each volatile write
    • Insert a loadload barrier after each volatile read
    • Insert a LoadStore barrier after each volatile read

Why does volatile not guarantee atomicity?

Compound operations on volatile variables (such as i++) are not atomic because i++ is a three-step operation from a bytecode perspective

In multithreaded environment, data calculation and data assignment may occur multiple times, that is, the operation is non-atomic. After the data is reloaded, if the count variable in the main memory is modified, the value in the thread working memory has been loaded before, so the change operation will not be changed accordingly. That is, variables in the private memory and public memory are not synchronized, leading to data inconsistency

For volatile variables, the JVM simply ensures that the values loaded from main memory into thread working memory are up to date, that is, when the data is loaded.

Analyze the following code using a memory barrier

public class VolatileTest { int i = 0; volatile boolean flag = false; public void write() { i = 1; flag = true; } public void read() { if (flag) { System.out.println("i=" + i); }}}Copy the code

Write operations:

  • On every volatile writeIn front of theInsert aStorestore barrier
  • On every volatile writebehindInsert aStoreload barrier
operation instructions
i = 1 Ordinary writing
Storestore barrier Disallow reordering of normal writes above and volatile writes below
flag = true Volatile write
Storeload barrier Disallow volatile writes above and possible volatile read/write reordering below

Read:

  • On every volatile readbehindInsert aLoadload barrier
  • On every volatile readbehindInsert aLoadstore barrier
operation instructions
if (flag) Volatile read
Loadload barrier Disallow volatile reads above and normal read reordering below
Loadstore barrier Disallow volatile reads above and normal write reordering below
System.out. Common reading