Study guide
13.2 Memory Visibility, `volatile`, Intrinsic Locks, and Synchronization
Oracle Java SE 17 Developer Certified Professional 1Z0-829
Track your progress as you read
Finished sections get a check mark.
1 Introduction: The 1Z0-829 Exam
About the Java SE 17 Developer (1Z0-829) Exam
2 Chapter 1: Java Fundamentals & Language Basics
1.1 Primitive Types, Literals, Wrapper Classes, and Type Promotion/Casting 1.2 Local Variable Type Inference (`var`) Rules and Scoping 1.3 Operators, Precedence, Evaluation Order, and Short-Circuit Evaluation 1.4 Text Blocks, String Immutability, and StringBuilder APIs
3 Chapter 2: Program Flow & Decision Constructs
2.1 Conditional Branching with if/else and the Ternary Operator 2.2 Switch Statements and Switch Expressions 2.3 Iteration Constructs, Loop Control, and Branching Labels
4 Chapter 3: Object-Oriented Design & Class Mechanics
3.1 Class Declarations, Field/Method Signatures, and Access Modifiers 3.2 Constructors, Overloading, Initialization Order, and Garbage Collection 3.3 Inheritance, Method Overriding, Polymorphism, and `super`/`this` 3.4 Abstract Classes, Static Members, and Object Methods (`equals`, `hashCode`, `toString`)
5 Chapter 4: Advanced OOP: Interfaces, Records & Sealed Types
4.1 Interface Architecture: Default, Static, and Private Methods 4.2 Enumerations: Constants, Constructors, Methods, and Custom Bodies 4.3 Records: Canonical/Compact Constructors, Components, and Immutability 4.4 Sealed Classes and Interfaces: `permits`, Hierarchy Constraints, and Modifiers
6 Chapter 5: Pattern Matching & Nested Types
5.1 Pattern Matching for `instanceof` and Flow Scoping Mechanics 5.2 Nested Types: Member Inner, Static Nested, Local, and Anonymous Classes 5.3 Defensive Programming, Immutability Patterns, and Validation
7 Chapter 6: Generics, Collections & Arrays
6.1 Generic Classes, Interfaces, Methods, and Type Erasure 6.2 Bounded Type Parameters and Wildcards (PECS Rule) 6.3 Collections Framework: List, Set, Queue, and Deque APIs 6.4 The Map Interface, Sorting, Comparable, and Comparator 6.5 Arrays: Declaration, Initialization, Multidimensional Arrays, and the Arrays Utility Class
8 Chapter 7: Functional Programming & Lambda Expressions
7.1 Functional Interfaces and Lambda Syntax Rules 7.2 Standard Functional Interfaces in `java.util.function` 7.3 Method References: Static, Bound, Unbound, and Constructor References
9 Chapter 8: The Stream API
8.1 Stream Lifecycle: Creation, Pipeline Architecture, and Lazy Evaluation 8.2 Intermediate Stream Operations: Filtering, Mapping, Sorting, and Slicing 8.3 Terminal Stream Operations: Reduction, Matching, Finding, and Collecting 8.4 Primitive Streams (`IntStream`, `LongStream`, `DoubleStream`) and Numeric Statistics
10 Chapter 9: Advanced Streams & Collectors
9.1 The Optional Class: Safe Null Handling, Transformation, and Chaining 9.2 The Collectors API: Downstream Reductions, Grouping, and Partitioning 9.3 Parallel Streams: Fork/Join Pool, Spliterators, and Concurrency Pitfalls
11 Chapter 10: Exception Handling
10.1 The Java Exception Hierarchy: Checked vs. Unchecked Exceptions and Errors 10.2 Exception Constructs: `try-catch-finally`, Multi-Catch, and Rethrowing 10.3 Try-With-Resources, `AutoCloseable`, Suppressed Exceptions, and Custom Exceptions
12 Chapter 11: Date/Time API & Localization
11.1 Date and Time Core Types: `LocalDate`, `LocalTime`, `LocalDateTime` 11.2 Timestamps, Intervals, and Durations: `Instant`, `Period`, and `Duration` 11.3 Time Zones, Daylight Saving Time (DST) Offsets, and `ZonedDateTime` 11.4 Internationalization and Localization: `Locale`, `ResourceBundle`, and Formatters 11.5 Formatting and Parsing Messages, Numbers, Percentages, and Localized Dates
13 Chapter 12: Java Platform Module System (JPMS)
12.1 Module Architecture, `module-info.java`, and Module Paths 12.2 Module Directives: `requires`, `requires transitive`, `exports`, and Qualified Exports 12.3 Reflection, Deep Encapsulation, `opens`, and Open Modules 12.4 Services in JPMS (`uses`, `provides...with`), Migration Paths, and Tooling (`jlink`, `jdeps`) 12.5 Compiling and Packaging Code: Non-Modular JARs, Modular JARs, and Migration Strategies
14 Chapter 13: Concurrency & Multithreading
13.1 Thread Fundamentals, `Runnable`, `Callable`, and Thread Lifecycle 13.2 Memory Visibility, `volatile`, Intrinsic Locks, and Synchronization 13.3 The Concurrency Framework: `ExecutorService`, Thread Pools, and `Future` 13.4 Concurrent Collections, Atomic Variables, Explicit Locks, and `CompletableFuture`
15 Chapter 14: Java I/O and NIO.2
14.1 Classical I/O: Byte and Character Streams, Readers, Writers, and Buffering 14.2 Object Serialization, `Serializable`, `transient`, and `serialVersionUID` 14.3 NIO.2 Architecture: `Path`, `Paths`, and Basic `Files` Operations 14.4 Advanced NIO.2: Directory Walking, Streams (`Files.lines`, `walk`), and `WatchService`
16 Chapter 15: Accessing Databases with JDBC
15.1 JDBC Architecture, Drivers, `DriverManager`, and `DataSource` Connections 15.2 SQL Execution: `Statement`, `PreparedStatement`, and `CallableStatement` 15.3 `ResultSet` Navigation, Data Types, and Transaction Management (Commit, Rollback, Savepoints)
17 Chapter 16: Annotations & the Java Logging API
16.1 Standard Annotations: @Override, @FunctionalInterface, @Deprecated, @SuppressWarnings, and @SafeVarargs 16.2 Java Logging API Basics: Logger, Levels, Handlers, and Configuration
13.2 Memory Visibility, `volatile`, Intrinsic Locks, and Synchronization
Key Takeaways Explain This Topic AI
Race conditions occur when multiple concurrent threads access shared mutable state without proper coordination, producing nondeterministic and incorrect results.
Every Java object possesses an intrinsic monitor; intrinsic locks are reentrant, allowing a thread that already holds a lock to re-acquire it without self-deadlock.
Synchronized instance methods lock on the current object instance (this), whereas synchronized static methods lock on the Class object (ClassName.class), operating on entirely distinct monitors.
The volatile modifier guarantees immediate memory visibility across CPU caches and prevents instruction reordering, but it does not provide atomicity for compound operations such as count++.
Deadlock arises when the four Coffman conditions (mutual exclusion, hold and wait, no preemption, circular wait) are met simultaneously; enforcing a consistent lock acquisition order breaks circular wait.
Last updated: October 2026
Memory Visibility, volatile , Intrinsic Locks, and Synchronization
When multiple threads read and modify shared memory simultaneously, software systems face two fundamental hazards: race conditions (threads interleaving execution in unpredictable orders) and visibility failures (one thread failing to see the memory writes executed by another thread due to compiler optimizations and CPU core caching). Java addresses these challenges through intrinsic synchronization, the volatile modifier, and the formal guarantees of the Java Memory Model (JMM).
Race Conditions, Critical Sections, and Data Races
Race Condition : A flaw in software execution timing where the correctness of a program depends on the relative timing or interleaving of concurrent execution paths.
Critical Section : A block of code that accesses shared mutable state and must be executed atomically by at most one thread at a time.
Data Race : Occurs when two or more concurrent threads access the same memory location simultaneously without synchronization, and at least one of those accesses is a write.
- The Non-Atomicity of Compound Operations
- A classic trap involves incrementing an integer counter across threads: public class Counter {
- private int count = 0;
- public void increment() {
count++; // SEEMINGLY ATOMIC, BUT ACTUALLY THREE DISTINCT OPERATIONS!
- }
- }
At the bytecode level, count++ breaks down into three non-atomic steps:
getfield : Read the current value of count from memory into a local operand stack.
iadd : Add 1 to the loaded value on the operand stack.
putfield : Write the incremented value from the operand stack back to the heap field.
If Thread A and Thread B read count simultaneously when its value is 5 , both compute 6 independently, and both write 6 back to memory. Two increments occurred, but the counter advances by only 1 —a lost update.
Intrinsic Monitors and Reentrancy
Every object in the Java heap possesses an associated intrinsic monitor (also called an intrinsic lock or monitor lock). The synchronized keyword uses this monitor to enforce mutual exclusion across critical sections.
Lock Reentrancy
Java's intrinsic locks are reentrant : lock acquisition is scoped to the thread , not the invocation. If a thread already holds the intrinsic monitor for Object X, it can enter any other code block or method that synchronizes on Object X without blocking: public class ReentrantExample {
- public synchronized void outerMethod() {
- // Thread acquires lock on 'this' (counter = 1)
- innerMethod();
- }
- public synchronized void innerMethod() {
- // Same thread re-acquires lock on 'this' (counter = 2)
- System.out.println("Inside innerMethod without deadlock!");
- } // Lock counter decremented to 1 upon exit
- } // Lock counter decremented to 0 and released upon exit
Without reentrancy, a synchronized method calling another synchronized method on the same object would deadlock with itself.
synchronized Methods vs. synchronized Blocks
Synchronization can be applied at the method level or scoped to explicit blocks.
Instance Methods vs. Static Methods
Instance Synchronized Method : Locks on the intrinsic monitor of the calling instance ( this ) : public synchronized void performTask() { /* Locks on this */ }
Static Synchronized Method : Locks on the intrinsic monitor of the java.lang.Class object corresponding to that class in the JVM classloader hierarchy: public static synchronized void logEvent() { /* Locks on Logger.class */ }
Warning
The Cross-Monitor Locking Trap An instance synchronized method and a static synchronized method in the same class do not serialize against each other . One thread can execute instance.performTask() while another thread simultaneously executes MyClass.logEvent() , because they synchronize on completely different monitor objects ( instance vs. MyClass.class ).
Synchronized Blocks and Explicit Lock Objects
Synchronized blocks provide fine-grained locking, minimizing the time a thread holds a monitor: public class BankAccount {
- private double balance;
- // Best practice: private final dedicated lock object
- private final Object balanceLock = new Object();
- public void deposit(double amount) {
- validateAmount(amount); // Executed outside lock (uncontended)
- synchronized (balanceLock) {
- balance += amount; // Critical section
- }
- }
- }
- Why private final Object is Best Practice
private : Prevents untrusted external code from synchronizing on the same lock object, eliminating unintended contention or external deadlocks.
final : Guarantees that the reference cannot be reassigned. If a lock reference changes, subsequent threads synchronize on different instances, violating mutual exclusion.
Avoid String Literals and Boxed Primitives : Never synchronize on String literals ( synchronized ("LOCK") ) or boxed primitives ( Integer.valueOf(1) ). String pooling and wrapper caching share instances across unrelated classes and libraries, causing JVM-wide lock contention.
Memory Visibility, Caching, and the volatile Keyword
Modern hardware employs hierarchical caches (L1, L2, L3) per CPU core. A thread running on Core 1 may cache a variable in L1 cache, while Core 2 modifies that variable in main memory. Without synchronization, Core 1 may continue reading stale data indefinitely. +-----------------------+ +-----------------------+
- | CPU Core 1 | | CPU Core 2 |
- | [L1/L2 Cache: flag=0] | | [L1/L2 Cache: flag=1] |
- +-----------------------+ +-----------------------+
- | |
- +-----------------+------------------+
- v
- +-----------------------+
- | Main Memory (RAM) |
- | flag = 0 (Stale!) |
- +-----------------------+
- The volatile Modifier
Declaring a field volatile establishes two guarantees:
Memory Visibility : Every write to a volatile variable is immediately flushed to main memory, and every read is fetched directly from main memory. CPU register and local cache values are invalidated.
Instruction Reordering Prevention : The compiler and CPU hardware are forbidden from reordering instructions across memory barriers (fences) placed around volatile reads and writes. public class TaskRunner {
private volatile boolean active = true;
- public void stop() {
- active = false; // Immediately visible to all threads
- }
- public void run() {
- while (active) {
- // Safely polls volatile variable without caching
- }
- }
- }
Warning
volatile Does NOT Ensure Atomicity! While volatile guarantees that reads and writes of a variable are visible across all threads, it does not make compound operations atomic. Declaring private volatile int count = 0; does not make count++ thread-safe. For atomic compound mutations, use synchronized , explicit locks, or atomic classes (e.g., AtomicInteger ).
The Java Memory Model (JMM) and Happens-Before
The JMM formalizes memory visibility through the happens-before relationship. If Action A happens-before Action B, then the memory writes performed by Action A are guaranteed to be visible to Action B.
Primary Happens-Before Rules
Program Order Rule : Within a single thread, each action happens-before every action that appears later in the source code.
Monitor Lock Rule : An unlock on an intrinsic monitor happens-before every subsequent lock on the same monitor.
Volatile Variable Rule : A write to a volatile field happens-before every subsequent read of that same field.
Thread Start Rule : A call to Thread.start() happens-before any action in the started thread.
Thread Termination Rule : Any action in a thread happens-before any other thread successfully completes a join() on that thread or detects isAlive() == false .
Transitivity : If A happens-before B, and B happens-before C, then A happens-before C.
Concurrency Hazards: Deadlock, Livelock, and Starvation
Hazard Definition Typical Cause Mitigation
Deadlock Two or more threads are permanently blocked, each waiting for a lock held by another Circular lock dependency Enforce global lock ordering; use tryLock()
Livelock Threads actively change states in response to each other, consuming CPU without making progress Over-reactive contention resolution Introduce randomized backoff delays
Starvation A runnable thread is perpetually denied CPU time or lock acquisition Greedy locks or unmanaged priorities Fair locking ( new ReentrantLock(true) )
The Four Coffman Conditions for Deadlock
Deadlock occurs if and only if all four conditions hold simultaneously:
Mutual Exclusion : At least one resource is held in a non-shareable mode.
Hold and Wait : A thread holds a resource while requesting another resource.
No Preemption : Resources cannot be forcibly confiscated from a thread.
Circular Wait : A closed chain of threads exists where each thread holds a lock needed by the next thread in the chain.