In modern applications, multithreading is an essential feature that enhances performance by allowing multiple threads to run concurrently. However, concurrent access to shared resources can lead to data inconsistency and unexpected behaviors, known as race conditions. To prevent these scenarios, synchronization techniques become crucial.
This blog will provide a comprehensive overview of various synchronization methods available in Java.
Every object in Java has an intrinsic lock, also known as a monitor. When a thread acquires the lock on an object, all other threads that try to access the same object must wait until the lock is released.
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
In the code above, the increment
and getCount
methods are synchronized. This ensures that only one thread can execute either of these methods at a time, preventing concurrent modifications to the count
variable.
Sometimes, you may want only a part of a method to be synchronized. In such cases, synchronized blocks provide a finer level of control, allowing you to limit the scope of synchronization.
class SharedResource { private int data; public void updateData(int value) { synchronized(this) { data = value; } } }
Here, only the code within the synchronized block is locked, allowing other parts of the updateData
method to execute concurrently without blocking.
volatile
KeywordThe volatile
keyword in Java signifies that a variable's value may be changed by different threads. Marking a variable as volatile ensures that reads and writes to it are directly read from and written to main memory, thus preventing caching effects.
class VolatileExample { private volatile boolean running = true; public void stop() { running = false; } public void run() { while (running) { // Do something } } }
In this example, the running
variable is marked volatile. Changes made by one thread are immediately visible to other threads, making it perfect for flags that control the execution of threads.
Java provides the java.util.concurrent.locks
package, which includes the ReentrantLock
class. This lock is more flexible than intrinsic locks, allowing features like try-lock and timed lock.
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class LockExample { private Lock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { return count; } }
The above example demonstrates how to utilize ReentrantLock
. Here, we explicitly acquire and release the lock using the lock()
and unlock()
methods, allowing for better handling of exceptions through the finally
block.
A semaphore is a signaling mechanism that helps control access to a particular resource by multiple threads. In Java, the Semaphore
class is used to maintain a set number of permits for resource access.
import java.util.concurrent.Semaphore; class SemaphoreExample { private final Semaphore semaphore = new Semaphore(2); // Allow 2 threads to access a resource public void accessResource() { try { semaphore.acquire(); // critical section } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); } } }
In this example, the semaphore is initialized with a count of 2, allowing two threads to access the critical section concurrently. Other threads will have to wait until a permit becomes available.
A CountDownLatch
is a synchronization aid that allows one or more threads to wait until a set of operations performed by other threads completes. You initialize it with a count, and each time a thread completes its task, it decrements the count.
import java.util.concurrent.CountDownLatch; class Task implements Runnable { private CountDownLatch latch; public Task(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try { // Simulate some work Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); } } } public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(new Task(latch)).start(); } latch.await(); // Wait for the count to reach zero System.out.println("All tasks completed!"); } }
In this example, three tasks are started, and the main thread waits for all tasks to complete before printing a message. The CountDownLatch
effectively manages synchronization without requiring excessive locking.
A CyclicBarrier
allows a set of threads to all wait for each other to reach a common barrier point. Once all threads reach the barrier, they are released simultaneously to continue execution.
import java.util.concurrent.CyclicBarrier; class Worker implements Runnable { private CyclicBarrier barrier; public Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { System.out.println("Worker is doing some work..."); try { Thread.sleep(1000); // Simulate work barrier.await(); // Wait at the barrier } catch (Exception e) { Thread.currentThread().interrupt(); } System.out.println("Worker has finished work and proceeding..."); } } public class CyclicBarrierExample { public static void main(String[] args) throws Exception { final int NUM_WORKERS = 3; CyclicBarrier barrier = new CyclicBarrier(NUM_WORKERS); for (int i = 0; i < NUM_WORKERS; i++) { new Thread(new Worker(barrier)).start(); } } }
In this illustration, each Worker
thread performs some work and waits at the barrier for all workers to finish before continuing. This is perfect for scenarios where tasks can proceed only once all threads are ready.
By utilizing these different synchronization techniques in Java, you can effectively manage multithreading in your applications while ensuring data integrity and preventing race conditions.
16/10/2024 | Java
23/09/2024 | Java
16/10/2024 | Java
30/10/2024 | Java
11/12/2024 | Java
23/09/2024 | Java
24/09/2024 | Java
24/09/2024 | Java
23/09/2024 | Java
23/09/2024 | Java
16/10/2024 | Java
24/09/2024 | Java