Learnitweb

Comparing synchronized vs StampedLock vs VarHandle for Concurrency Control in Java

Overview

Java provides multiple mechanisms to control access to shared resources in a concurrent environment:

FeatureSynchronized BlockStampedLockVarHandle
TypeIntrinsic LockExplicit LockLow-Level Memory Access
Read/Write SupportNo distinctionYes (Optimistic, Pessimistic)No lock abstraction
ReentrancyYesNoNot applicable
FairnessNot guaranteedNot guaranteedNot applicable
Best Use CaseSimplicity, legacyHigh contention with readsAtomic field updates
Java VersionSince Java 1.0Java 8Java 9

Let’s explore each in detail.

1. synchronized Block

Concept

synchronized is a monitor-based locking mechanism provided since Java 1.0. It allows mutual exclusion, ensuring that only one thread at a time can execute the synchronized block or method.

Syntax

synchronized (lockObject) {
    // critical section
}

Or:

public synchronized void method() {
    // critical section
}

Characteristics

  • Reentrant: A thread can acquire the same lock multiple times.
  • Thread-safe: Mutual exclusion.
  • Fairness: Not guaranteed.
  • Blocking: Threads are blocked if the lock is not available.
  • No read-write distinction: All threads are blocked regardless of read/write intent.

Use Case

Simple thread-safe access to shared resources, especially when code needs to be protected at method or block level.

Example

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

2. StampedLock

Concept

StampedLock was introduced in Java 8 to enhance performance, especially in read-heavy concurrent applications.

Unlike ReentrantReadWriteLock, StampedLock provides:

  • Read Lock
  • Write Lock
  • Optimistic Read (non-blocking and fast)

It returns a stamp/token that must be used for unlocking.

Syntax

StampedLock lock = new StampedLock();

long stamp = lock.writeLock();
try {
    // write
} finally {
    lock.unlockWrite(stamp);
}

Key Features

  • Not Reentrant: A thread must not try to acquire a lock it already holds.
  • Optimistic Read: Lightweight, allows reads without blocking.
  • Pessimistic Locking: Available for both read and write.
  • Performance: Better for scenarios with many readers and few writers.
  • Complexity: More error-prone due to manual unlocking and stamp management.

Use Case

High-performance applications where reads vastly outnumber writes and the risk of data races is low.

Example

public class StampedLockCounter {
    private final StampedLock lock = new StampedLock();
    private int count = 0;

    public void increment() {
        long stamp = lock.writeLock();
        try {
            count++;
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    public int getCount() {
        long stamp = lock.tryOptimisticRead();
        int current = count;
        if (!lock.validate(stamp)) {
            stamp = lock.readLock();
            try {
                current = count;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return current;
    }
}

3. VarHandle

Concept

VarHandle (introduced in Java 9) provides low-level atomic access to variables, similar to sun.misc.Unsafe but safe and standardized.

VarHandles give fine-grained control over memory visibility and atomic operations like compare-and-set, get-and-set, etc.

Syntax

VarHandle handle = MethodHandles.lookup()
    .in(MyClass.class)
    .findVarHandle(MyClass.class, "field", int.class);

Then you can use:

handle.getVolatile(myObject);
handle.setVolatile(myObject, value);
handle.compareAndSet(myObject, expected, newValue);

Key Features

  • Low-level, atomic operations.
  • Memory fences: Full control over memory visibility.
  • Supports volatile, acquire/release semantics.
  • Fine-tuned concurrency.
  • Non-blocking.
  • No locking: Doesn’t prevent other threads from accessing the variable.

Use Case

When building lock-free, non-blocking algorithms and data structures. Replaces some use cases of AtomicInteger, Unsafe, etc.

Example

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class VarHandleCounter {
    private volatile int count;
    private static final VarHandle COUNT_HANDLE;

    static {
        try {
            COUNT_HANDLE = MethodHandles.lookup()
                .findVarHandle(VarHandleCounter.class, "count", int.class);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public void increment() {
        int prev;
        do {
            prev = (int) COUNT_HANDLE.getVolatile(this);
        } while (!COUNT_HANDLE.compareAndSet(this, prev, prev + 1));
    }

    public int getCount() {
        return (int) COUNT_HANDLE.getVolatile(this);
    }
}

Comparison Table

FeaturesynchronizedStampedLockVarHandle
API TypeKeywordClass-basedClass-based
Locking TypeBlocking, exclusiveRead/Write, OptimisticLock-free
ReentrantYesNoNot applicable
Read/Write SeparationNoYesNo
FairnessNot guaranteedNot guaranteedNot applicable
Optimistic ReadsNoYesNo
Lock Acquisition CostHighModerateLow
Lock OverheadHigh under contentionBetter scalabilityNo locking overhead
ComplexityLowMediumHigh
Suitable forSimplicityRead-heavy systemsFine-grained atomic operations
Thread BlockingYesYes (except optimistic)No
Example UseSimple shared objectsCaching, statistics, countersCustom atomic structures

When to Use What?

ScenarioUse
Simple mutual exclusionsynchronized
Read-heavy workloadsStampedLock
Low-level concurrency with fine controlVarHandle
Need for reentrancysynchronized
Need for optimistic, non-blocking readsStampedLock
Building lock-free data structuresVarHandle