Understanding Java Lock Mechanisms and Thread Communication

Lock Types in Java Concurrency

Fair vs Unfair Locks

Fair locks ensure threads acquire locks in the order they requested them, preventing thread starvation but potentially reducing throughput. Unfair locks allow threads to acquire locks out of order, wich can improve performance but may lead to some threads waiting indefinitely.

// Creating a fair lock
ReentrantLock fairLock = new ReentrantLock(true);

// Default behavior: unfair lock
ReentrantLock defaultLock = new ReentrantLock();

Reentrant Locks

Both synchronized blocks and explicit Lock implementations in Java are reentrant. This means a thread that already holds a lock can acquire it again without deadlocking. The lock maintains a hold count that increments with each acquisition and decrements with each release.

Explicit Lock Implementation

Since JDK 5, Java provides explicit locking mechanisms through the Lock interface, offering more flexibility than synchronized blocks.

public class Worker implements Runnable {
    private static final ReentrantLock accessLock = new ReentrantLock();
    
    public void performTask() {
        accessLock.lock();
        try {
            // Critical section code
            System.out.println("Executing critical section");
        } finally {
            accessLock.unlock();
        }
    }
    
    @Override
    public void run() {
        performTask();
    }
}

Thread Interview Questions

Synchronized vs Lock Comparison

  • Syntax: synchronized is keyword-based, Lock is API-based
  • Flexibility: Lock allows try-lock, timed lock, and interruptible lock acquisition
  • Condition Variables: Lock supports multiple Condition objects for finer-grained control
  • Performance: Lock may offer better performance under high contention

Lock Release Scenarios

Locks are released when:

  • Synchronized method/block completes normally
  • Thread encounters return or break statement
  • Uncaught exception or error occurs
  • Thread calls wait() method

Locks are NOT released when:

  • Thread calls sleep() or yield()
  • Thread is suspended using deprecated suspend() method

Advanced Lock Exercise: Thread Coordination

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class PrintCoordinator {
    private int sequence = 1;
    private final ReentrantLock mutex = new ReentrantLock();
    private final Condition firstCondition = mutex.newCondition();
    private final Condition secondCondition = mutex.newCondition();
    private final Condition thirdCondition = mutex.newCondition();
    
    public void executeFirstPhase() {
        mutex.lock();
        try {
            while (sequence != 1) {
                firstCondition.await();
            }
            
            for (int i = 0; i < 3; i++) {
                System.out.println("Phase 1 - Step " + (i + 1));
            }
            
            sequence = 2;
            secondCondition.signal();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            mutex.unlock();
        }
    }
    
    public void executeSecondPhase() {
        mutex.lock();
        try {
            while (sequence != 2) {
                secondCondition.await();
            }
            
            for (int i = 0; i < 2; i++) {
                System.out.println("Phase 2 - Step " + (i + 1));
            }
            
            sequence = 3;
            thirdCondition.signal();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            mutex.unlock();
        }
    }
    
    public void executeThirdPhase() {
        mutex.lock();
        try {
            while (sequence != 3) {
                thirdCondition.await();
            }
            
            System.out.println("Phase 3 - Final Step");
            
            sequence = 1;
            firstCondition.signal();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            mutex.unlock();
        }
    }
}

Thread Communication

Inter-Thread Coordination Example

Using two threads to print numbers alternately demonstrates basic thread communication:

class NumberPrinter implements Runnable {
    private int counter = 100;
    
    @Override
    public void run() {
        while (counter > 0) {
            synchronized (this) {
                notifyAll();
                System.out.println(Thread.currentThread().getName() + ": " + counter);
                counter--;
                
                if (counter > 0) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
    }
}

Communication Methods

  • wait(): Pauses current thread and releases lock
  • notify(): Wakes one waiting thread
  • notifyAll(): Wakes all waiting threads

sleep() vs wait()

Modern Thread Creation Approaches

Using Callable Interface

Callable offers advantages over Runnable:

  • Returns a result
  • Can throw checked exceptions
  • Supports generics
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class DataProcessor implements Callable<string> {
    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return "Processed data";
    }
}

public class CallableExample {
    public static void main(String[] args) throws Exception {
        DataProcessor processor = new DataProcessor();
        FutureTask<string> task = new FutureTask<>(processor);
        
        new Thread(task).start();
        
        // Non-blocking check
        if (task.isDone()) {
            System.out.println("Result: " + task.get());
        }
        
        // Blocking retrieval
        String result = task.get();
        System.out.println("Final result: " + result);
    }
}
</string></string>

Thread Pool Implementation

Thread pools provide efficient thread management:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadPoolDemo {
    public static void main(String[] args) throws Exception {
        // Create fixed-size thread pool
        ExecutorService pool = Executors.newFixedThreadPool(10);
        
        // Submit Runnable task
        pool.execute(() -> {
            System.out.println("Running in pool: " + Thread.currentThread().getName());
        });
        
        // Submit Callable task
        Future<string> future = pool.submit(() -> {
            Thread.sleep(500);
            return "Task completed";
        });
        
        System.out.println("Future result: " + future.get());
        
        // Graceful shutdown
        pool.shutdown();
    }
}
</string>

Benefits of Thread Pools

  • Performance: Reduces thread creation overhead
  • Resource Management: Controls concurrent thread count
  • Responsiveness: Improves application reaction time
  • Monitoring: Provides centralized thread management

Tags: java Concurrency Lock multithreading ThreadPool

Posted on Tue, 12 May 2026 15:06:11 +0000 by jgires