Java concurrent lock example

๐Ÿ” Understanding Java Concurrent Locks (java.util.concurrent.locks.Lock)

When writing multi-threaded programs in Java, thread safety becomes a key concern.
Traditionally, developers have used the synchronized keyword to protect critical sections.
However, Java’s java.util.concurrent.locks.Lock interface offers greater flexibility and control for concurrency management.


⚙️ What Is a Lock?

A Lock is a more advanced synchronization mechanism than the synchronized block.
It allows explicit acquisition and release of locks and supports features unavailable in synchronized blocks, such as:

  • Timed and interruptible lock acquisition,

  • Separate lock/unlock methods across scopes,

  • Fairness policies, and

  • Better performance in high-contention scenarios.

Since Lock is an interface, you must use one of its implementations —
most commonly ReentrantLock.


๐Ÿ” Lock vs. synchronized — Key Differences

FeaturesynchronizedLock (e.g., ReentrantLock)
ScopeMust be fully contained within one method or blocklock() and unlock() can be called in different methods
TimeoutCannot wait with timeouttryLock(long time, TimeUnit unit) allows timeout
InterruptibilityCannot be interrupted while waitingLock acquisition can be interrupted
Fairness PolicyNot configurableCan specify fairness (FIFO order)
PerformanceSimpler but less flexibleBetter for highly concurrent applications

๐Ÿ’ป Example — Using ReentrantLock for Thread Safety

Let’s see how to use a concurrent lock to safely simulate email sending from multiple threads.

๐Ÿ“ Code Example

package com.vinod.thread; import java.util.Date; import java.util.concurrent.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Demonstrates how to use ReentrantLock for thread-safe operations. * * @author vinod */ public class ConcurrentLockExample { public static void main(String[] args) { EmailProcessor emailProcessor = new EmailProcessor(); int n = 25; ExecutorService exec = Executors.newFixedThreadPool(n); for (int i = 0; i < n; ++i) { exec.submit(new SendEmailThread(emailProcessor)); } exec.shutdown(); } } /** * Service that simulates sending emails using a shared resource. */ class EmailProcessor { private final Lock emailLock = new ReentrantLock(); public void sendEmail() { emailLock.lock(); // Acquire lock before entering critical section try { System.out.println(Thread.currentThread().getName() + " - Sending email at " + new Date()); Thread.sleep(1000); // Simulate email sending delay } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("Email sending interrupted"); } finally { System.out.println(Thread.currentThread().getName() + " - Email sending completed"); emailLock.unlock(); // Always release lock in finally block } } } /** * Callable task that sends an email via EmailProcessor. */ class SendEmailThread implements Callable<String> { private final EmailProcessor emailProcessor; public SendEmailThread(EmailProcessor processor) { this.emailProcessor = processor; } @Override public String call() { emailProcessor.sendEmail(); return "Success"; } }

๐Ÿงพ Example Output

pool-1-thread-1 - Sending email at Sat Nov 02 12:35:21 PST 2025 pool-1-thread-1 - Email sending completed pool-1-thread-2 - Sending email at Sat Nov 02 12:35:22 PST 2025 pool-1-thread-2 - Email sending completed pool-1-thread-3 - Sending email at Sat Nov 02 12:35:23 PST 2025 pool-1-thread-3 - Email sending completed ...

Each thread waits for the lock to be released before sending its email, ensuring thread safety even when multiple threads try to access the shared resource simultaneously.


๐Ÿง  How It Works — Step by Step

  1. Main thread creates an ExecutorService with 25 threads.

  2. Each thread submits a SendEmailThread task.

  3. Every task calls emailProcessor.sendEmail().

  4. Inside sendEmail(), the ReentrantLock ensures only one thread executes the email-sending block at a time.

  5. After sending, the lock is released in the finally block, allowing another thread to proceed.


๐Ÿงฉ Why Use Locks Instead of synchronized

While synchronized is simpler for basic use cases, Lock provides:

  • More precise control over locking and unlocking.

  • Ability to back off after waiting (e.g., using tryLock()).

  • Better visibility and diagnostics for debugging concurrent issues.


๐Ÿงฑ Example with Timeout (Optional Enhancement)

Here’s how you can use tryLock() with a timeout:

if (emailLock.tryLock(2, TimeUnit.SECONDS)) { try { // Perform safe operations } finally { emailLock.unlock(); } } else { System.out.println("Could not acquire lock, skipping..."); }

This avoids blocking indefinitely if another thread holds the lock for too long.


๐Ÿงญ Key Takeaways

ConceptSummary
Lock InterfaceProvides explicit locking control
ReentrantLockMost commonly used Lock implementation
Best PracticeAlways unlock in a finally block
tryLock()Enables timeout-based lock attempts
When to UseHigh concurrency scenarios where synchronized is too restrictive


 

No comments:

Post a Comment

Model Context Protocol (MCP) — Complete Guide for Backend Engineers

  Model Context Protocol (MCP) — Complete Guide for Backend Engineers Build Tools, Resources, and AI-Driven Services Using LangChain Moder...

Featured Posts