As a rookie, I read a lot of articles written by the big boys, trying to stand on their shoulders to understand how Volatile works. These articles have answered a lot of doubts in my mind, among which some questions were gradually understood after looking for a lot of information. For me, this is very meaningful.

When we think of volatile in Java, we might easily think of the following associated terms:

  1. Instruction rearrangement
  2. The memory barrier
  3. CPU Cache Consistency
  4. visibility
  5. atomic

Instruction rearrangement

public class Singleton {
    public static Singleton singleton;

    private Singleton(a) {};

    public static Singleton getInstance(a) {
        if (singleton == null) {
            synchronized (singleton) {
                if (singleton == null) {
                    singleton = newSingleton(); }}}returnsingleton; }}Copy the code

For example, the new Singleton() in the above code can be divided into three steps:

  1. Allocating memory space
  2. Initialize an object
  3. Assigns the address of the memory space to the corresponding reference

If instruction reorder occurs:

  1. Allocating memory space
  2. Assigns the address of the memory space to the corresponding reference.
  3. Initialize an object

Is it possible for another thread to get an uninitialized object? Volatile can solve this problem through memory barriers

The memory barrier

1. LoadLoad Load1 – > LoadLoad – >Load2: Ensure that the data to be read by Load1 is fully read before the data to be read by Load2 and subsequent read operations are accessed

2. StoreStore Store1 – >StoreStore – >Store2: Ensure that the write operations of Store1 are visible to other processors before Store2 and subsequent write operations are executed

LoadStore Load1 – >LoadStore – >Store2: Ensure that the data to be read by Load1 is fully read before Store2 and subsequent write operations are flushed out

4. StoreLoad Store1 – > StoreLoad – >Load2: Ensure that writes to Store1 are visible to all processors before Load2 and all subsequent reads are executed. It is the most expensive of the four barriers. In most processor implementations, this barrier is a universal barrier that doubles as the other three memory barriers

To implement volatile memory semantics, the compiler inserts memory barriers into the instruction sequence to prevent reordering of a particular type of processor when the bytecode is generated. In JMM, the compiler inserts memory barriers against volatile when the bytecode is generated:

  1. Insert a StoreStore barrier before each volatile write: ensure that prior writes are updated to main memory before volatile writes
  2. After each volatile write, a StoreLoad barrier is inserted to ensure that volatile writes succeed and prevent instruction reordering until subsequent variables are read
  3. Insert a LoadLoad barrier after each volatile read: : Before volatile reads, ensure that the previous variable reads are successful and prevent rearrangements of previous reads and volatile reads
  4. Insert a LoadStore barrier after each volatile read: ensure that the following writes are performed after a volatile read succeeds, preventing subsequent reads and volatile read rearrangements

CPU Cache Consistency

10 graphs open the door to CPU cache consistency

visibility

If we know some of the features of Volatile from the web:

  1. We put the JVMWorking memory and main memorySuch asCPU L1 and L2 Cache and CPU L1 Cache and main memory
  2. Thread 1Changes to volatile variables are immediately synchronized to main memory, while allowingThread 2 working memoryBecomes invalid,Thread 2To read the value, the latest value is fetched from main memory again
  3. How does Step 2 work? This is whereThe system bus,sniffer

To quote one of the most popular words on the Internet:

When CPU0 updates main memory, it sends a notification to the system bus. CPU1 sniffed the notification and set its L1 L2 Caceh to invalid. When a thread on CPU1 reads the value, it finds that the value is invalid and reads it from main memory

What is the process from CPU0 sending a notification to CPU1 invalidating the cache? If CPU0 updates main memory first and then sends notifications, will CPU1 not read the old data while CPU1 invalidates its cache Settings?

2. If CPU0 sends a notification to update main memory, isn’t it possible that CPU1 will be able to read the old data in main memory between the time CPU1’s cache expires and the time CPU0 successfully updates main memory?

So how exactly is this notification done?

First, CPU0 will not write to main memory immediately. Assume that CPU0’s cache behavior is M(modified), and CPU1’s cache line is marked as I(invalid). When CPU1 reads an invalid cache line, it notifies CPU0, and then reports the modified result to main memory and CPU1’s cache line, marked as S

Reference links www.zhihu.com/question/29…

atomic

We know that volatile guarantees visibility, so why does it not guarantee atomicity? Here are a few questions:

1. Why can’t other threads make atomicity changes to volatile even if other CPU caches fail? The data that was modified by the thread has been written back to the main memory. After the thread switch, the cache will be invalid and the other thread will read the data back into the cache. 2. Won’t memory barriers be inserted before and after volatile? So why is i++ not safe?

Here’s the clearest explanation I’ve seen: Volatile atomicity

Memory barriers are thread-safe, but the instructions that precede them are not. At some point thread 1 takes the value load from I, places it in the CPU cache, and then places it in register A, which increments by 1(register A holds the intermediate value and does not modify I directly, so no other thread gets the incremented value). If thread 2 does the same at this point, it gets the value I ==10, increments by 1 to 11, and then immediately flusher main memory. At this time, thread 2 modified the value of I, and the cache of the value of I ==10 in thread 1 became invalid, and it was read from the main memory again and changed to 11. Next, thread 1 resumes and assigns the value of register A, 11, to CPU cache I. This raises the issue of thread safety

Volicate modifier object

Private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private, private

I have so many questions:

  1. whenvolicateWhen modifying an object: objects are placed inThe heapIf we change a property in this object, the address of this object hasn’t changed, isn’t the data read from the heap? That doesn’t mean that in this case, usevolicateWhat’s the point of decorating an object? Unless we’re assigning a new object to the reference, right?
  2. whenvolicateWhen decorating an array:

Hope big guy can answer

Refer to the connection

  1. 10 graphs open the door to CPU cache consistency
  2. Zhihu: Volatile visibility analysis
  3. Zhihu: Atomicity analysis for volatile
  4. Meituan Technology team: Research on Java memory access reordering
  5. Concurrency Programming Network: In-depth analysis of how Volatile is implemented
  6. Concurrency programming network: Whether volatile guarantees visibility of elements in an array
  7. CopyOnWriteArrayList class set method confusion