Fundamentals 12 min read

Mastering Locks: From Pessimistic to Distributed Concurrency Control

This article explains why locks are essential in programming, categorizes various lock types such as pessimistic, optimistic, spin, fair, reentrant, and distributed locks, and shows how to choose and implement them in Java for high‑concurrency, high‑availability, and high‑performance systems.

Architect's Alchemy Furnace
Architect's Alchemy Furnace
Architect's Alchemy Furnace
Mastering Locks: From Pessimistic to Distributed Concurrency Control

Locks appear everywhere in daily life, but in software they are markers that control access to shared resources, ensuring atomic operations and preventing race conditions, especially in multi‑threaded or distributed environments.

Common lock classifications include pessimistic vs. optimistic, spin vs. adaptive spin, no‑lock, biased, lightweight, heavyweight, fair vs. unfair, reentrant vs. non‑reentrant, and exclusive vs. shared locks.

Pessimistic and Optimistic Locks

Pessimistic lock assumes data may be modified by others, so it acquires a lock before reading or writing, causing other threads to block until the lock is released. Traditional relational databases use row, table, read, and write locks, and Java's synchronized and ReentrantLock are examples of pessimistic locks.

Optimistic lock assumes data will not be changed by others; reads proceed without locking, and writes verify that the data has not been altered (using version numbers or CAS). Java's volatile and atomic classes in java.util.concurrent.atomic implement optimistic locking.

Optimistic locks suit read‑heavy, write‑light scenarios, while pessimistic locks fit write‑heavy, read‑light cases.

Spin Locks and Adaptive Spin Locks

A spin lock keeps a thread looping while waiting for a lock, avoiding costly context switches. An adaptive spin lock adjusts the spin duration based on recent lock acquisition history, spinning longer when the lock is likely to become available soon.

Spin locks improve performance for short‑lived locks but waste CPU cycles if the lock is held too long. Common implementations include TicketLock, CLHLock, and MCSLock.

No‑Lock, Biased, Lightweight, and Heavyweight Locks

These represent four states of a Java object’s lock, stored in the object header’s Mark Word. The states transition from biased → lightweight → heavyweight as contention increases.

No‑lock: no synchronization, threads can read but only one can successfully modify.

Biased lock: optimizes uncontended synchronization by biasing the lock toward a single thread.

Lightweight lock: used when contention appears; threads spin before upgrading.

Heavyweight lock: falls back to OS mutex when spinning fails, causing threads to block.

Fair and Unfair Locks

Fair locks grant the lock to the longest‑waiting thread, while unfair locks may allow a thread to acquire the lock out of order, improving throughput at the cost of predictability.

In Java, new ReentrantLock(true) creates a fair lock, and new ReentrantLock(false) creates an unfair lock. Choose fair locks when thread wait time exceeds lock acquisition time; otherwise prefer unfair locks for higher performance.

Reentrant and Non‑Reentrant Locks

Reentrant locks allow the same thread to acquire the same lock multiple times without deadlock (e.g., synchronized and ReentrantLock). Non‑reentrant locks block if the owning thread attempts to reacquire them.

Exclusive and Shared Locks

Exclusive locks (e.g., synchronized, ReentrantLock) allow only one thread to hold the lock. Shared locks (read locks) permit multiple threads to read concurrently but prevent writes; ReentrantReadWriteLock provides a read lock (shared) and a write lock (exclusive).

Distributed Locks

In distributed systems, a lock must coordinate across multiple processes or machines. Implementations store the lock flag in a shared memory service such as Redis, Zookeeper, a database, or Consul, ensuring mutual exclusion despite network latency and failures.

// Pessimistic lock with synchronized
public synchronized void test() { /* operation */ }

// Pessimistic lock with ReentrantLock
Lock lock = new ReentrantLock();
public void testLock() {
    lock.lock();
    // TODO: operation
    lock.unlock();
}

// Optimistic lock using CAS
AtomicInteger atomicInteger = new AtomicInteger();
public void testCAS() {
    atomicInteger.incrementAndGet();
}

// Fair lock example
ReentrantLock fairLock = new ReentrantLock(true);

// Unfair lock example
ReentrantLock unfairLock = new ReentrantLock(false);
Javadistributed-systemsoptimistic lockingpessimistic locking
Architect's Alchemy Furnace
Written by

Architect's Alchemy Furnace

A comprehensive platform that combines Java development and architecture design, guaranteeing 100% original content. We explore the essence and philosophy of architecture and provide professional technical articles for aspiring architects.

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.