Let’s look at atomicity, visibility, and order in the Java memory model:


Atomicity, visibility, and orderliness in the Java memory model

  • atomic

Refers to an operation that is performed atomically. Either the operation is not performed; Or execute atomically, that is, without being interrupted by another thread.

  • visibility

Visibility refers to the visibility between threads, where the modified state of one thread is visible to another thread. The two keywords that can achieve visibility in Java are synchronized and volatile

  • order

If viewed from within a thread, all operations are ordered; if viewed from within another thread, all thread operations are unordered. The first part of the sentence refers to the “sequential semantics in the thread”, the latter part refers to the “instruction reordering” phenomenon and “working memory and main memory synchronization delay” phenomenon. The Java language provides the keywords volatile and synchronized to ensure order between threads


Synchronized implements atomicity (synchronization) and visibility of multiple threads.

Two JVM rules about Synchronized:

  • The thread must flush the latest value of the shared variable to main memory before it can be unlocked.

  • When a thread locks, it empties the value of a shared variable in working memory, so that when using a shared variable, it needs to read the latest value from main memory (note: locking and unlocking require the same lock).

Synchronized The process of executing mutually exclusive code

  • Get the mutex

  • Clear working memory

  • Copies the latest copy of a variable from main memory to working memory

  • Execute the code

  • Flush the value of the changed shared variable to main memory

  • Release the mutex

Volatile guarantees visibility of variables, but does not guarantee atomicity of compound operations

How volatile implements memory visibility:

In depth: this is done by adding memory barriers and disallowing reordering optimizations

  • Execute on volatile variablesThe write operationIs added after the write operationStore barrier instruction.
  • Execute on volatile variablesA read operationIs added before the read operationLoad barrier instruction.
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.Copy the code

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

Applicable usage scenarios for volatile

Use volatile variables instead of locks in a limited number of cases. For volatile variables to provide ideal thread-safety, both conditions must be met:

  • Writes to variables do not depend on their current value
  • This variable is not contained in an invariant with other variables.

The first condition restricts volatile variables from being used as thread-safe counters. Although the delta operation (x++) looks like a single operation, it is actually a combination of a sequence of (read-modify-write) operations that must be performed atomically, and volatile does not provide the necessary atomic properties. The correct operation requires that the value of x remain constant for the duration of the operation, which volatile variables do not. (However, if you only write from a single thread, you can ignore the first condition.)

Conclusion:

  • Volatile is lighter than synchronized.

  • Volatile is not as widely used as synchronized.

  • Volatile does not require locking and is lighter than synchronized and does not block threads.

  • From a memory visibility perspective, volatile reads are equivalent to locking, and volatile writes are equivalent to unlocking.

  • Synchronized guarantees both visibility and atomicity, while volatile guarantees visibility but not atomicity.

  • Volatile itself does not guarantee atomicity of the get and set operations, only that changes are visible. But Java’s memory model guarantees that get and set operations on volatile long and double variables are atomic.