google-site-verification: google61fe8ba583a51912.html
Thread Synchronization in Java

Thread synchronization is crucial in multi-threaded programming to prevent thread interference and memory consistency errors when multiple threads access shared resources.

The Problem: Race Conditions

public class Counter { private int count = 0; public void increment() { count++; // Not thread-safe! } public int getCount() { return count; } } public class RaceConditionDemo { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); // Expected: 2000, but often gets less due to race condition System.out.println("Final count: " + counter.getCount()); } }

Synchronization Solutions

1. Synchronized Methods

public class SynchronizedCounter { private int count = 0; // Synchronized method - locks on 'this' public synchronized void increment() { count++; } public synchronized void decrement() { count--; } public synchronized int getCount() { return count; } }

2. Synchronized Blocks

public class SynchronizedBlockCounter { private int count = 0; private final Object lock = new Object(); // Explicit lock object public void increment() { synchronized (lock) { count++; } } public int getCount() { synchronized (lock) { return count; } } // Synchronized block on different objects public void transfer(SynchronizedBlockCounter from, int amount) { // Avoid deadlock by ordering locks SynchronizedBlockCounter first, second; if (System.identityHashCode(from) < System.identityHashCode(this)) { first = from; second = this; } else { first = this; second = from; } synchronized (first) { synchronized (second) { // Transfer logic } } } }

3. Static Synchronization

public class StaticSynchronizedExample { private static int staticCount = 0; private int instanceCount = 0; // Locks on Class object (StaticSynchronizedExample.class) public static synchronized void incrementStatic() { staticCount++; } // Synchronized block on class object public static void decrementStatic() { synchronized (StaticSynchronizedExample.class) { staticCount--; } } // Instance method - locks on 'this' public synchronized void incrementInstance() { instanceCount++; } }

Advanced Synchronization Mechanisms

1. ReentrantLock

import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; private boolean available = false; private final Condition condition = lock.newCondition(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // Always unlock in finally block } } // Producer-Consumer with Conditions public void produce(int value) throws InterruptedException { lock.lock(); try { while (available) { condition.await(); // Wait until item is consumed } count = value; available = true; System.out.println("Produced: " + value); condition.signalAll(); // Notify consumers } finally { lock.unlock(); } } public void consume() throws InterruptedException { lock.lock(); try { while (!available) { condition.await(); // Wait until item is produced } System.out.println("Consumed: " + count); available = false; condition.signalAll(); // Notify producers } finally { lock.unlock(); } } public boolean tryIncrement() { if (lock.tryLock()) { try { count++; return true; } finally { lock.unlock(); } } return false; } }

2. ReadWriteLock

import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private String data = ""; public void writeData(String newData) { rwLock.writeLock().lock(); try { data = newData; System.out.println("Data written: " + newData); Thread.sleep(1000); // Simulate write time } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { rwLock.writeLock().unlock(); } } public String readData() { rwLock.readLock().lock(); try { System.out.println("Data read: " + data); Thread.sleep(500); // Simulate read time return data; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return ""; } finally { rwLock.readLock().unlock(); } } }

Thread-Safe Collections

1. Concurrent Collections

import java.util.concurrent.*; import java.util.*; public class ConcurrentCollectionsExample { public static void main(String[] args) { // Thread-safe collections ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>(); CopyOnWriteArrayList<String> copyOnWriteList = new CopyOnWriteArrayList<>(); ConcurrentLinkedQueue<String> concurrentQueue = new ConcurrentLinkedQueue<>(); // Using concurrent map concurrentMap.put("key1", 1); concurrentMap.putIfAbsent("key1", 2); // Won't replace existing // Atomic operations concurrentMap.compute("key1", (k, v) -> v == null ? 1 : v + 1); concurrentMap.merge("key2", 1, Integer::sum); } }

2. Synchronized Collections Wrappers

public class SynchronizedCollectionsExample { public static void main(String[] args) { // Creating synchronized collections List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>()); Set<String> syncSet = Collections.synchronizedSet(new HashSet<>()); // When iterating, must synchronize manually synchronized (syncList) { Iterator<String> it = syncList.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } } }

Atomic Variables

import java.util.concurrent.atomic.*; public class AtomicExample { private final AtomicInteger atomicCount = new AtomicInteger(0); private final AtomicReference<String> atomicReference = new AtomicReference<>("initial"); private final AtomicLong atomicLong = new AtomicLong(0L); public void increment() { atomicCount.incrementAndGet(); // Atomic operation } public void updateReference() { // Atomic compare-and-set String current, next; do { current = atomicReference.get(); next = current + "-updated"; } while (!atomicReference.compareAndSet(current, next)); } public int accumulate(int value) { return atomicCount.accumulateAndGet(value, Math::max); } }

Volatile Keyword

public class VolatileExample { private volatile boolean running = true; private volatile int counter = 0; public void stop() { running = false; // Immediately visible to other threads } public void increment() { counter++; // Not atomic! Use for visibility only } public void example() { Thread worker = new Thread(() -> { while (running) { // Do work System.out.println("Working..."); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } System.out.println("Stopped"); }); worker.start(); // Stop after 5 seconds try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } stop(); // Stops the worker thread } }

Deadlock Prevention

public class DeadlockPrevention { private final Object lock1 = new Object(); private final Object lock2 = new Object(); private int resource1 = 0; private int resource2 = 0; // POTENTIAL DEADLOCK public void method1() { synchronized (lock1) { synchronized (lock2) { resource1++; resource2--; } } } public void method2() { synchronized (lock2) { synchronized (lock1) { // Different order - DEADLOCK! resource1--; resource2++; } } } // DEADLOCK-FREE VERSION public void method1Safe() { synchronized (lock1) { synchronized (lock2) { resource1++; resource2--; } } } public void method2Safe() { synchronized (lock1) { // Same order as method1Safe synchronized (lock2) { resource1--; resource2++; } } } // Using tryLock to avoid deadlocks public boolean tryTransfer() { ReentrantLock lock1 = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); while (true) { if (lock1.tryLock()) { try { if (lock2.tryLock()) { try { // Do work return true; } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } // Avoid livelock try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } } }

Producer-Consumer Pattern

import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class ProducerConsumerExample { private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); public class Producer implements Runnable { public void run() { int value = 0; while (true) { try { queue.put(value); System.out.println("Produced: " + value); value++; Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } } public class Consumer implements Runnable { public void run() { while (true) { try { Integer value = queue.take(); System.out.println("Consumed: " + value); Thread.sleep(150); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } } public void start() { Thread producer = new Thread(new Producer()); Thread consumer = new Thread(new Consumer()); producer.start(); consumer.start(); } }

Best Practices

  1. Minimize synchronization scope - synchronize only critical sections
  2. Use concurrent collections when possible instead of manual synchronization
  3. Prefer immutability - immutable objects are inherently thread-safe
  4. Use atomic variables for simple atomic operations
  5. Avoid nested locks to prevent deadlocks
  6. Always unlock in finally blocks
  7. Document thread safety in your classes
// Thread safety documentation examples @ThreadSafe public class ThreadSafeClass { // Fully synchronized - safe for concurrent access } @Immutable public final class ImmutableClass { // No synchronization needed - immutable } @NotThreadSafe public class NotThreadSafeClass { // Client must synchronize externally }

Common Pitfalls

  1. Synchronizing on different objects for related operations
  2. Forgotten synchronization in some methods
  3. Synchronizing on mutable fields
  4. Excessive synchronization causing performance issues
  5. Ignoring iteration synchronization

Thread synchronization is essential for writing correct multi-threaded applications in Java. Choose the right synchronization mechanism based on your specific needs and always test thoroughly under concurrent conditions.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper