Thread Synchronization

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:

  1. Synchronized Methods and Blocks
  2. .Reentrant Locks
  3. Volatile Keyword
  4. Atomic Variables
  5. Wait/Notify Mechanism
  6. Semaphore
  7. 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.

Scroll to Top