Types of Locks

About

Locks in Java help synchronize access to shared resources in multithreaded environments. They prevent race conditions and ensure data consistency. There are multiple types of locks based on their scope, usage, and implementation.

1. Object-Level Lock

An object-level lock is associated with a specific instance of a class. It ensures that only one thread can execute a synchronized method/block on a given object at a time.

Example

class SharedResource {
    public synchronized void display() {  // Object-level lock
        System.out.println(Thread.currentThread().getName() + " executing...");
    }
}
  • If two threads call display() on the same object, they will execute one after another.

  • If two threads call display() on different objects, they will execute independently.

Equivalent Using Explicit Lock

import java.util.concurrent.locks.ReentrantLock;

class SharedResource {
    private final ReentrantLock lock = new ReentrantLock();

    public void display() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " executing...");
        } finally {
            lock.unlock();
        }
    }
}

2. Class-Level Lock (Static Synchronization)

A class-level lock is used to synchronize static methods or blocks. It prevents multiple threads from executing static methods of the same class simultaneously.

  • Affects all instances of the class.

  • Only one thread can execute any synchronized static method at a time.

Example

class SharedResource {
    public static synchronized void display() {  // Class-level lock
        System.out.println(Thread.currentThread().getName() + " executing...");
    }
}

Equivalent Using Explicit Lock

import java.util.concurrent.locks.ReentrantLock;

class SharedResource {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void display() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " executing...");
        } finally {
            lock.unlock();
        }
    }
}

3. Method-Level Lock

A method-level lock is applied by synchronizing an entire method. It applies object-level or class-level locking, depending on whether the method is instance or static.

Example (Instance Method)

class SharedResource {
    public synchronized void display() {  // Implicit object-level lock
        System.out.println(Thread.currentThread().getName() + " executing...");
    }
}

Equivalent to synchronized(this) {} inside the method.

Example (Static Method)

class SharedResource {
    public static synchronized void display() {  // Implicit class-level lock
        System.out.println(Thread.currentThread().getName() + " executing...");
    }
}

Equivalent to synchronized(SharedResource.class) {} inside the method.

4. Block-Level Lock

A block-level lock restricts synchronization to a specific block inside a method instead of locking the entire method.

Example (Object-Level)

class SharedResource {
    public void display() {
        synchronized (this) {  // Locking only this block
            System.out.println(Thread.currentThread().getName() + " executing...");
        }
    }
}

Other methods can still run while this block is locked.

Example (Class-Level)

class SharedResource {
    public void display() {
        synchronized (SharedResource.class) {  // Locking the entire class
            System.out.println(Thread.currentThread().getName() + " executing...");
        }
    }
}

Synchronization affects all threads calling static methods

5. Reentrant Lock

A Reentrant Lock allows the same thread to acquire the same lock multiple times without causing a deadlock.

Example

import java.util.concurrent.locks.ReentrantLock;

class SharedResource {
    private final ReentrantLock lock = new ReentrantLock();

    public void display() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " executing...");
        } finally {
            lock.unlock();
        }
    }
}
  • Unlike synchronized, ReentrantLock provides:

    • Try Locking (tryLock())

    • Interruptible Locking (lockInterruptibly())

    • Fair Locking (new ReentrantLock(true))

6. Fair and Unfair Locks

  • Fair Lock: Threads are scheduled in the order they requested the lock.

  • Unfair Lock (default): The lock may not follow strict ordering.

Example

ReentrantLock fairLock = new ReentrantLock(true);  // Enables fairness
  • Fair locks prevent thread starvation, but can reduce performance.

7. Read-Write Lock

A ReadWriteLock allows:

  • Multiple readers to access a resource concurrently.

  • Only one writer to access the resource exclusively.

  • Useful for caching and database operations.

Example

import java.util.concurrent.locks.ReentrantReadWriteLock;

class SharedResource {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void read() {
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " reading...");
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void write() {
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " writing...");
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

8. Stamped Lock

A StampedLock improves upon ReadWriteLock by:

  • Optimistic Reads (reading without blocking writes).

  • Explicit stamp validation.

  • Provides better performance than ReadWriteLock

Example

import java.util.concurrent.locks.StampedLock;

class SharedResource {
    private final StampedLock lock = new StampedLock();

    public void read() {
        long stamp = lock.tryOptimisticRead();
        System.out.println(Thread.currentThread().getName() + " reading optimistically...");
        if (!lock.validate(stamp)) {  // Check if a write happened
            stamp = lock.readLock();
            try {
                System.out.println(Thread.currentThread().getName() + " reading after acquiring lock...");
            } finally {
                lock.unlockRead(stamp);
            }
        }
    }

    public void write() {
        long stamp = lock.writeLock();
        try {
            System.out.println(Thread.currentThread().getName() + " writing...");
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}.

9. Spin Lock

A Spin Lock makes threads actively wait (busy-waiting) instead of blocking.

  • Used in low-latency applications but wastes CPU cycles.

Example

import java.util.concurrent.atomic.AtomicBoolean;

class SpinLock {
    private AtomicBoolean lock = new AtomicBoolean(false);

    public void lock() {
        while (!lock.compareAndSet(false, true)) {
            // Busy-wait
        }
    }

    public void unlock() {
        lock.set(false);
    }
}

Comparison

Lock Type
Scope
Use Case
Pros
Cons

Object-Level

Instance

Prevent multiple threads from modifying an object

Simple

Blocks entire method

Class-Level

Static Methods

Synchronize across all instances

Ensures global lock

Affects all threads

Method-Level

Entire Method

Prevent concurrent execution of a method

Easy to use

Can be inefficient

Block-Level

Inside a Method

Synchronize specific part of a method

More efficient

Requires careful implementation

ReentrantLock

Custom

More control over locking

Advanced features

Requires manual unlocking

ReadWriteLock

Read-Write

Allows concurrent reads

Faster reads

Complexity

StampedLock

Read-Write

Optimistic reads for performance

High efficiency

Difficult to implement

Spin Lock

CPU-bound

High-performance, low-latency apps

No context switching

Wastes CPU

Last updated

Was this helpful?