Thread synchronization in Java is essential to ensure that multiple threads can safely access shared resources without causing data corruption or inconsistency. Java provides several mechanisms for thread synchronization:
- Synchronized Methods and Blocks
- .Reentrant Locks
- Volatile Keyword
- Atomic Variables
- Wait/Notify Mechanism
- Semaphore
- CountDownLatch
Synchronized Methods and Blocks
Using the synchronized keyword, you can synchronize methods or blocks of code to ensure mutual exclusion.
Program Implementation
1.Synchronized Methods
//SynMethCounter.java public class SynMethCounter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } public static void main(String[] args) { SynchronizedCounter counter = new SynchronizedCounter(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + counter.getCount()); } } /* C:\>javac SynMethCounter.java C:\>java SynMethCounter Final count: 2000 */
2.Synchronized Blocks
//SynBlockCounter.java public class SynBlockCounter { private int count = 0; private final Object lock = new Object(); public void increment() { synchronized (lock) { count++; } } public int getCount() { synchronized (lock) { return count; } } public static void main(String[] args) { SynBlockCounter counter = new SynBlockCounter(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + counter.getCount()); } } /* C:\>javac SynBlockCounter.java C:\>java SynBlockCounter Final count: 2000 */
Reentrant Locks
ReentrantLocks provide more flexibility than synchronized blocks and methods, allowing for more complex locking mechanisms.
//ReentrantLockCounter.java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockCounter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } public static void main(String[] args) { ReentrantLockCounter counter = new ReentrantLockCounter(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + counter.getCount()); } } /* C:\>javac ReentrantLockCounter.java C:\>java ReentrantLockCounter Final count: 2000 */
Volatile Keyword
The volatile keyword in Java is used to indicate that a variable’s value will be modified by different threads. It ensures visibility of changes to variables across threads.
private volatile dataType variableName;
public class SharedData {
private volatile boolean flag = false; // volatile variable
public void setFlagTrue() {
flag = true;
}
public boolean isFlagSet() {
return flag;
}
}
Code language: PHP (php)
//VolatileDemo.java public class VolatileDemo { private volatile boolean running = true; public void stop() { running = false; } public void start() { while (running) { System.out.println("Running..."); } System.out.println("Stopped."); } public static void main(String[] args) throws InterruptedException { VolatileDemo example = new VolatileDemo(); Thread thread = new Thread(example::start); thread.start(); Thread.sleep(3); example.stop(); } } /* C:\>javac VolatileDemo.java C:\>java VolatileDemo Running... Running... Running... Stopped. */
Atomic Variables
Java provides atomic classes (e.g., AtomicInteger, AtomicBoolean) for thread-safe operations on single variables without using synchronization.
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private final AtomicInteger count = new AtomicInteger(0); // Atomic variable
public void increment() {
count.incrementAndGet(); // Atomic operation
}
public int getCount() {
return count.get(); // Safe read
}
}
Code language: PHP (php)
import java.util.concurrent.atomic.AtomicInteger; //AtomicCounter.java public class AtomicCounter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.getAndIncrement(); } public int getCount() { return count.get(); } public static void main(String[] args) { AtomicCounter counter = new AtomicCounter(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + counter.getCount()); } } /* C:\>javac AtomicCounter.java C:\>java AtomicCounter Final count: 2000 */
Wait/Notify Mechanism
The wait() and notify() methods are used for inter-thread communication.
From java.lang.Object class
public final void wait() throws InterruptedException
public final void wait(long timeoutMillis) throws InterruptedException
public final void wait(long timeoutMillis, int nanos) throws InterruptedException
public final void notify()
public final void notifyAll()
Code language: PHP (php)
Semaphore
A semaphore controls access to a resource with a set number of permits.
import java.util.concurrent.Semaphore;
public class Example {
private final Semaphore semaphore = new Semaphore(permits); // permits = number of available "passes"
public void accessResource() throws InterruptedException {
semaphore.acquire(); // Acquire a permit (wait if none available)
try {
// critical section code
} finally {
semaphore.release(); // Release the permit
}
}
}
Code language: PHP (php)
//SemaphoreDemo.java import java.util.concurrent.Semaphore; public class SemaphoreDemo { private final Semaphore semaphore = new Semaphore(1); public void accessResource() { try { semaphore.acquire(); System.out.println("Resource acquired by " + Thread.currentThread().getName()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("Resource released by " + Thread.currentThread().getName()); semaphore.release(); } } public static void main(String[] args) { SemaphoreDemo example = new SemaphoreDemo(); Runnable task = example::accessResource; Thread thread1 = new Thread(task,"Paani"); Thread thread2 = new Thread(task,"Mahesh"); thread1.start(); thread2.start(); } } /* C:\>javac SemaphoreDemo.java C:\>java SemaphoreDemo Resource acquired by Paani Resource released by Paani Resource acquired by Mahesh Resource released by Mahesh C:\>java SemaphoreDemo Resource acquired by Mahesh Resource released by Mahesh Resource acquired by Paani Resource released by Paani
CountDownLatch
A CountDownLatch allows one or more threads to wait until a set of operations being performed by other threads completes.
import java.util.concurrent.CountDownLatch;
CountDownLatch latch = new CountDownLatch(count); // Initialize with count (number of times latch must be decremented)
latch.await(); // Wait until the latch reaches zero
latch.countDown(); // Decrement the count of the latch by 1
Code language: JavaScript (javascript)
//CountDownLatchDemo.java import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { private final CountDownLatch latch = new CountDownLatch(2); public void performTask() { System.out.println("Task performed by " + Thread.currentThread().getName()); latch.countDown(); } public void waitForCompletion() { try { latch.await(); System.out.println("All tasks completed. Proceeding."); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { CountDownLatchDemo example = new CountDownLatchDemo(); Runnable task = example::performTask; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); example.waitForCompletion(); } } /* C:\>javac CountDownLatchDemo.java C:\>java CountDownLatchDemo Task performed by Thread-0 Task performed by Thread-1 All tasks completed. Proceeding. C:\>java CountDownLatchDemo Task performed by Thread-0 Task performed by Thread-1 All tasks completed. Proceeding. */
The above techniques illustrate various ways to synchronize threads in Java, ensuring thread-safe operations and proper inter-thread communication.