Fundamentals 39 min read

Why Does Thread A Always Grab the Lock First? A Deep Dive into Java Synchronization, JVM Internals, and CPU Fundamentals

This article explores the evolution from early mechanical calculators to modern CPUs, explains thread states, synchronization mechanisms, memory barriers, volatile semantics, and JVM lock implementations, and reveals why Java's notify method consistently awakens a specific waiting thread.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
Why Does Thread A Always Grab the Lock First? A Deep Dive into Java Synchronization, JVM Internals, and CPU Fundamentals

Computer History Overview

Early claims that the abacus or the I‑Ching inspired binary are debunked; the true ancestors of computers are 17th‑century European mechanical calculators invented by Blaise Pascal and later refined by Gottfried Wilhelm Leibniz, who also introduced binary notation.

The first electronic computer, ENIAC (1946), occupied a whole building, consumed massive power, and operated at only 5 000 operations per second, highlighting the dramatic performance leap to modern processors.

CPU Principles and Architecture

A CPU fetches binary instructions from memory via control, address, and data buses, decodes them, and executes them in the ALU. Modern CPUs use caches (L1, L2, L3) to bridge the speed gap between registers and main memory, typically with 64‑byte cache lines.

Cache‑coherency protocols ensure that multiple cores see a consistent view of shared data.

Thread Basics and States

Threads transition through states such as NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, and TERMINATED. The article provides Java examples that illustrate each state using Thread.start(), LockSupport.park(), synchronized, and wait/notify.

Synchronization and Locks

Synchronization serializes access to critical sections. The synchronized keyword acquires a monitor, which can be in thin (biased), lightweight, or heavyweight (inflated) mode. Lock escalation occurs when contention increases.

Reentrant locks ( ReentrantLock) support lockInterruptibly() to allow interruption while waiting for a lock.

Memory Barriers and Volatile

Memory barriers ( mfence, sfence, lfence on x86) prevent reordering of loads and stores across the barrier. The Java volatile keyword enforces a happens‑before relationship, guaranteeing visibility and ordering without full synchronization.

JVM Memory Model and Atomic Operations

Atomic classes (e.g., AtomicInteger) implement lock‑free operations using CAS (compare‑and‑swap). The underlying native method Unsafe.compareAndSwapInt maps to the hardware lock cmpxchg instruction, providing atomicity at the CPU level.

Lock Implementations and Performance

Two main lock types exist in HotSpot: heavyweight (OS‑managed) and lightweight (CAS‑based). Heavyweight locks are used when contention is high; lightweight locks are faster for short critical sections. The choice depends on lock‑hold time and the number of contending threads.

Thread Wake‑up Mechanisms

When notify() is called, the JVM removes a thread from the wait set (a doubly‑linked list) and places it into the entry list, which is then serviced before threads waiting in the contention queue (cxq). This explains why a waiting thread (Thread A) is often awakened before a competing thread (Thread C).

Common Pitfalls and Interview Questions

Using Thread.stop(), suspend(), or resume() is unsafe.

Interrupting a thread blocked on a synchronized lock does not release the lock.

Proper use of volatile, LockSupport.unpark(), and join() avoids deadlocks.

Understanding the JVM’s ObjectMonitor queues (wait set, cxq, entry list) is essential for debugging lock‑ordering issues.

Illustrative Code Snippets

Thread state demo:

Thread t = new Thread(() -> {
    System.out.println("run start");
    try { Thread.sleep(500); } catch (InterruptedException e) {}
    System.out.println("run end");
});

CAS example with AtomicInteger:

AtomicInteger counter = new AtomicInteger(0);
while (true) {
    int cur = counter.get();
    int next = cur + 1;
    if (counter.compareAndSet(cur, next)) break;
}

LockInterruptibly usage:

ReentrantLock lock = new ReentrantLock();
Thread t = new Thread(() -> {
    try { lock.lockInterruptibly(); /* work */ } catch (InterruptedException e) {}
});

Memory barrier illustration (native): Unsafe.compareAndSwapInt(obj, offset, expected, update); Overall, the article provides a comprehensive, low‑level view of how computers evolved, how CPUs execute instructions, and how Java’s threading model maps onto JVM and hardware primitives, offering valuable insight for developers and interview preparation.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CAScomputer historyCPU architectureJava concurrencythread synchronizationJVM internalsmemory barriers
Senior Brother's Insights
Written by

Senior Brother's Insights

A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.