ProgrammingJava Developer

What is the Java Memory Model (JMM) and how does it affect multithreaded programming?

Pass interviews with Hintsage AI assistant

Answer.

Background:

When Java first appeared, there was no formal description of how memory and threads interact. As a result, the same programs could behave differently on different JVMs, leading to hard-to-trace bugs. In 2004, the Java Memory Model (JMM) was added to the Java specification to strictly define how threads see updates to variables.

The Problem:

Without a clear memory model, read and write operations can be rearranged by the compiler or processor, leading to unexpected behavior when accessing shared variables from different threads. This can cause race conditions, visibility issues, and hard-to-debug synchronization errors.

The Solution:

The JMM defines the rules: when changes made by one thread become visible to others. It establishes the concepts of happens-before, synchronizers (synchronized, volatile, final), internal locks, and limits instruction reordering in a multithreaded environment.

Example code:

class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int get() { return count; } }

Key Features:

  • JMM defines when changes to one variable are visible to other threads
  • Using synchronized or volatile guarantees the proper publication of changes
  • Incorrect usage can lead to incorrect behavior even in the absence of explicit errors

Tricky Questions.

Why does volatile not make operations atomic, but only ensures visibility?

Volatile guarantees only the visibility of changes between threads, but not the atomicity of modifications. For example, incrementing a volatile variable is not an atomic operation since the action consists of reading, modifying, and writing.

Example code:

volatile int count = 0; count++; // Not atomic!

Can a synchronized block ensure the visibility of changes outside of it?

Yes. After exiting a synchronized block, all changes made inside are visible to other threads that enter the block on the same object monitor.

When do changes to final fields become visible to other threads?

Final fields guarantee correct publication only if the object is fully constructed before passing references to it to other threads, for example, through immutable objects. Otherwise, visibility of an uninitialized state may occur.

Common Mistakes and Anti-Patterns

  • Using multithreading without synchronization on variables shared between threads
  • Relying solely on volatile for complex operations
  • Publishing unconstructed objects between threads

Real-life Example

Negative Case

The developer decided to increment a website access counter using a simple volatile increment from multiple threads.

Pros:

  • Simpler, no need to worry about synchronization

Cons:

  • Actual loss of increments under high load due to lack of atomicity
  • Race condition and incorrect statistics

Positive Case

Used AtomicInteger for the counter or synchronized methods.

Pros:

  • Guarantee of correctness under any load
  • No loss of updates

Cons:

  • Slight decrease in performance due to synchronization