Study guide

9.2 Thread Safety, Synchronization, and the Java Memory Model

Oracle Java SE 21 Developer 1Z0-830

Track your progress as you read

Finished sections get a check mark.

1 Chapter 1: Java Basics, Primitive Types, and Operators

1.1 Program Structure, Primitives, Literals, Scopes, and var 1.2 Operators, Precedence, and Type Casting 1.3 Strings, Text Blocks, and StringBuilder 1.4 Date and Time APIs (java.time)

2 Chapter 2: Controlling Program Flow & Pattern Matching

2.1 Conditional Logic: if-else and Ternary Operators 2.2 Switch Statements and Switch Expressions 2.3 Pattern Matching for switch and instanceof 2.4 Loops and Branching Statements

3 Chapter 3: Object-Oriented Design & Class Architecture

3.1 Class Declarations, Constructors, and Object Lifecycle 3.2 Encapsulation and Access Modifiers 3.3 Inheritance, Method Overriding, and Polymorphism 3.4 Abstract Classes and Interfaces 3.5 Nested, Inner, Local, and Anonymous Classes 3.6 Method Overloading, Overload Resolution, and Varargs

4 Chapter 4: Modern Java Data Modeling: Records, Enums, and Sealed Types

4.1 Java Records, Canonical/Compact Constructors, and Component Accessors 4.2 Java Enums: Fields, Methods, and Abstract Constant-Specific Behavior 4.3 Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture 4.4 Advanced Record Patterns (JEP 440), Nested Deconstruction, and Sealed Hierarchy Exhaustiveness

5 Chapter 5: Exception Handling and Annotations

5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions 5.2 Exception Handling: try, catch, finally, and Multi-Catch 5.3 Resource Management: try-with-resources and AutoCloseable 5.4 Custom Exceptions and Best Practices 5.5 Java Annotations: Built-in, Meta-Annotations, and Custom Types

6 Chapter 6: Generics and the Collections Framework

6.1 Generics Fundamentals and Generic Methods 6.2 Wildcards, Type Bounds, and Type Erasure 6.3 Core Collections: Lists, Sets, Queues, and Maps 6.4 Sequenced Collections in Java 21 6.5 Sorting, Comparable, and Comparator 6.6 Arrays: Creation, Multidimensional Forms, and the Arrays Utility Class

7 Chapter 7: Functional Programming & Lambda Expressions

7.1 Built-in Functional Interfaces in java.util.function 7.2 Lambda Expressions, Variable Scopes, and Effectively Final 7.3 Method References: Four Varieties and Edge Cases 7.4 Working with java.util.Optional

8 Chapter 8: The Stream API and Data Pipelines

8.1 Stream Creation and Intermediate Operations 8.2 Terminal Operations and Reductions 8.3 Advanced Collectors, Grouping, Partitioning, and Teeing 8.4 Primitive Streams and Parallel Stream Processing

9 Chapter 9: Concurrency, Multithreading, and Virtual Threads

9.1 Thread Fundamentals, Creation, and Lifecycle 9.2 Thread Safety, Synchronization, and the Java Memory Model 9.3 Concurrency Utilities: ExecutorService, Callable, and Future 9.4 Concurrent Collections, Atomics, and Explicit Locks 9.5 Virtual Threads in Java 21 (Carrier Threads, Pinning, and Best Practices)

10 Chapter 10: The Java Platform Module System (JPMS)

10.1 Module Architecture and module-info.java Declarations 10.2 Module Directives: requires, exports, opens, provides, and uses 10.3 Modular Compilation, Packaging, and CLI Execution 10.4 Module Types, Migration Strategies, ServiceLoader SPI, and JLink

11 Chapter 11: Java I/O, NIO.2, and Object Serialization

11.1 Classic Java I/O Streams and Readers/Writers 11.2 Java NIO.2 File API: Path, Paths, and File System Operations 11.3 Advanced NIO.2 Stream Operations and Directory Traversal 11.4 Java Object Serialization and Record Serialization

12 Chapter 12: Implementing Localization and Internationalization

12.1 Locales and Resource Bundles 12.2 Formatting and Parsing Numbers, Currencies, Dates, Times, and Messages

9.2 Thread Safety, Synchronization, and the Java Memory Model

Key Takeaways Explain This Topic AI

Data races and race conditions occur when multiple threads access shared mutable state without proper synchronization, resulting in non-deterministic corrupted states.

Every Java object possesses an intrinsic lock (monitor); synchronized instance methods lock on the instance (this), while synchronized static methods lock on the declaring Class object.

Java intrinsic locks are fully reentrant: a thread holding a monitor can enter nested synchronized blocks guarded by that same monitor without causing a deadlock.

The volatile keyword establishes a happens-before memory ordering relationship guaranteeing immediate cross-thread visibility and inhibiting instruction reordering, but does not provide atomicity for compound operations.

Liveness hazards include deadlock (circular lock acquisition graph), livelock (active state changes without progress), and starvation (perpetual resource denial by higher-priority threads).

Last updated: September 2026

Thread Safety, Synchronization, and the Java Memory Model

Writing concurrent software in Java requires ensuring correctness across threads executing concurrently over shared data. Without synchronization, concurrent programs suffer from data races , memory visibility discrepancies , and thread liveness failures .

1. Race Conditions and Shared Mutable State

A class is thread-safe if it behaves correctly when accessed from multiple concurrent threads, regardless of scheduling interleavings, without requiring external synchronization by the caller.

The Root Cause: Shared Mutable State

Thread safety bugs arise when two or more threads access shared, mutable state and at least one thread performs a write operation.

Common Race Condition Patterns:

Read-Modify-Write : A thread reads a value, modifies it locally, and writes it back (e.g., count++ ). If two threads interleave:

  • Thread A reads count = 5
  • Thread B reads count = 5
  • Thread A writes count = 6
  • Thread B writes count = 6 (One update is lost!)

Check-Then-Act : A thread observes a condition and acts upon it, but the condition changes before the action completes: // Unsafe Lazy Initialization (Check-Then-Act race condition)

  • public class UnsafeSingleton {
  • private static UnsafeSingleton instance;
  • public static UnsafeSingleton getInstance() {

if (instance == null) { // Thread A and B both see null!

instance = new UnsafeSingleton(); // Two instances created!

  • }
  • return instance;
  • }
  • }
  • 2. Intrinsic Locks and the synchronized Keyword

Java provides built-in mutual exclusion through Intrinsic Locks (also called Monitor Locks ). Every object in Java has an associated monitor lock.

The Three Forms of synchronized :

  • A. Synchronized Instance Method
  • Locks on the instance itself ( this ): public class Counter {
  • private int count = 0;
  • public synchronized void increment() { // Locks on 'this'
  • count++;
  • }
  • }
  • B. Synchronized Static Method

Locks on the java.lang.Class object corresponding to the enclosing class ( Counter.class ): public class Counter {

private static int globalCount = 0;

  • public static synchronized void incrementGlobal() { // Locks on Counter.class
  • globalCount++;
  • }
  • }

Important

A synchronized instance method and a synchronized static method do NOT block each other! They acquire completely different locks: one locks on this (the specific object instance), while the other locks on the Class object ( Counter.class ).

C. Synchronized Statement Block

Locks on an explicitly specified object reference. This is generally preferred because it minimizes lock scope and allows private dedicated lock objects: public class BankAccount {

  • private double balance;
  • private final Object lock = new Object(); // Private final dedicated monitor
  • public void deposit(double amount) {
  • // Non-critical operations (logging, validation) outside lock
  • if (amount <= 0) throw new IllegalArgumentException();
  • synchronized (lock) { // Critical Section
  • balance += amount;
  • }
  • }
  • }
  • 3. Monitor Reentrancy

Intrinsic locks in Java are Reentrant . If a thread already holds the intrinsic lock on an object, it can enter other synchronized blocks or methods guarded by the same lock without deadlocking itself.

The JVM implements reentrancy by tracking the owning thread and an acquisition count :

When a thread enters a synchronized block, the count increments to 1.

When the owning thread enters a nested synchronized block on the same object, the count increments to 2.

As each block exits, the count decrements. When the count reaches 0, the monitor is fully released. public class ReentrantDemo {

  • public synchronized void outer() {
  • System.out.println("In outer");

inner(); // Re-acquires monitor on 'this' without blocking!

}

  • public synchronized void inner() {
  • System.out.println("In inner");
  • }
  • }
  • 4. The Java Memory Model (JMM) and volatile

Modern computer architectures feature multi-level CPU hardware caches (L1, L2, L3) and out-of-order execution pipelines. When thread A writes to a variable in its CPU register/cache, thread B running on another CPU core may not see the updated value in main memory, resulting in memory visibility discrepancies . +----------------+ +----------------+

  • | CPU Core 1 | | CPU Core 2 |
  • | [L1/L2 Cache] | | [L1/L2 Cache] |
  • | flag = true | | flag = false |
  • +----------------+ +----------------+
  • | |
  • +-------------[ Bus ]-----------+
  • |
  • +-------------------+
  • | Main Memory |
  • | flag = false |
  • +-------------------+
  • The volatile Keyword

Declaring a field as volatile guarantees:

Memory Visibility : Writes to a volatile field are immediately flushed to main memory, and reads always invalidate the local CPU cache to read directly from main memory.

Instruction Reordering Barriers : The compiler and CPU hardware are forbidden from reordering reads and writes across a volatile memory barrier.

volatile vs. synchronized (The Atomicity Trap!)

volatile guarantees visibility and ordering , but does NOT guarantee atomicity for compound operations!

Operations like count++ (read-modify-write) are NOT atomic , even if count is volatile . public class VolatileCounter {

private volatile int count = 0;

public void increment() {

count++; // NON-ATOMIC! Multiple threads can still lose updates!

  • }
  • }
  • 5. Happens-Before Memory Relationship

The Java Memory Model defines formal ordering guarantees through the Happens-Before relationship. If action X X X happens-before action Y Y Y , then the memory modifications of X X X are guaranteed to be visible to Y Y Y .

Fundamental Happens-Before Rules:

Program Order Rule : Each action in a single thread happens-before every action in that thread that comes later in program order.

Monitor Lock Rule : An unlock on an intrinsic lock monitor happens-before every subsequent lock acquisition on that same monitor.

Volatile Variable Rule : A write to a volatile field happens-before every subsequent read of that same volatile field.

Thread Start Rule : A call to Thread.start() on a thread happens-before any action in the started thread's run() method.

Thread Termination Rule : Any action in a thread happens-before any other thread successfully returns from a Thread.join() on that thread.

Transitivity Rule : If A A A happens-before B B B , and B B B happens-before C C C , then A A A happens-before C C C .

  • 6. Liveness Hazards: Deadlock, Livelock, and Starvation
  • A. Deadlock

A Deadlock occurs when two or more threads are permanently blocked, each holding a lock that the other needs, waiting in a circular dependency. // CLASSIC DEADLOCK EXAMPLE

  • Object lockA = new Object();
  • Object lockB = new Object();
  • // Thread 1 acquires lockA then tries to acquire lockB
  • Thread t1 = new Thread(() -> {
  • synchronized (lockA) {
  • try { Thread.sleep(50); } catch (Exception e) {}
  • synchronized (lockB) {
  • System.out.println("T1 acquired both");
  • }
  • }
  • });
  • // Thread 2 acquires lockB then tries to acquire lockA
  • Thread t2 = new Thread(() -> {
  • synchronized (lockB) {
  • try { Thread.sleep(50); } catch (Exception e) {}
  • synchronized (lockA) {
  • System.out.println("T2 acquired both");
  • }
  • }
  • });

t1.start();

t2.start(); // DEADLOCK OCCURS!

The Four Necessary Conditions for Deadlock (Coffman Conditions):

Mutual Exclusion : Resources cannot be shared.

Hold and Wait : Threads hold resources while waiting for others.

No Preemption : Resources cannot be forcibly confiscated.

Circular Wait : A closed chain of threads exists where each thread holds a resource needed by the next.

Deadlock Prevention:

Always acquire multiple locks in a globally strict, consistent canonical order (e.g., sort locks by system identity hash code or resource ID before acquiring).

B. Livelock

A Livelock occurs when threads continuously change their states in response to each other, but make no actual computational forward progress (analogous to two polite pedestrians trying to step around each other in a narrow hallway, stepping in the same direction indefinitely).

C. Starvation

Starvation occurs when a runnable thread is perpetually denied CPU execution time or lock access because other, more aggressive or higher-priority threads monopolize the shared resource.

7. Classic Coordination: wait() , notify() , and notifyAll()

The Object class provides low-level coordination methods:

wait() : Causes the current thread to release the object's monitor and enter the WAITING state until another thread invokes notify() or notifyAll() on the same monitor.

notify() : Wakes up a single arbitrary thread waiting on this object's monitor.

notifyAll() : Wakes up all threads waiting on this object's monitor.

Caution

wait() , notify() , and notifyAll() must only be called from inside a synchronized block/method on that exact object. Calling them without holding the monitor throws IllegalMonitorStateException .

Always invoke wait() inside a while loop checking the predicate condition, never an if statement, to guard against spurious wakeups ! // Correct wait/notify idiom

synchronized (lock) {

while (!conditionReady) { // Always loop on condition!

  • lock.wait(); // Releases lock while waiting
  • }
  • // Perform action once condition is true
  • }

Loading diagram... Memory Visibility, CPU Caches, and Happens-Before Barrier