1. java
  2. /concurrency
  3. /synchronization

Master Java Synchronization and Thread Safety

Java Synchronization

Synchronization in Java is the mechanism that controls access to shared resources by multiple threads. When multiple threads access shared data concurrently, synchronization prevents data corruption and ensures thread safety. Java provides several synchronization mechanisms to coordinate thread execution and maintain data consistency.

Why Synchronization is Needed

Without proper synchronization, concurrent access to shared data can lead to race conditions, data corruption, and unpredictable behavior.

Problems Without Synchronization:

  1. Race Conditions: Multiple threads modifying shared data simultaneously
  2. Data Inconsistency: Partial updates visible to other threads
  3. Lost Updates: One thread's changes overwritten by another
  4. Memory Visibility: Changes made by one thread not visible to others
  5. Atomicity Violations: Compound operations interrupted mid-execution
public class SynchronizationNeed {
    
    // Unsafe counter without synchronization
    static class UnsafeCounter {
        private int count = 0;
        
        public void increment() {
            // This is actually three operations:
            // 1. Read current value
            // 2. Add 1 to it  
            // 3. Write back the result
            count++; // Race condition can occur here!
        }
        
        public int getCount() {
            return count;
        }
    }
    
    public static void demonstrateRaceCondition() throws InterruptedException {
        System.out.println("=== Race Condition Demonstration ===");
        
        UnsafeCounter counter = new UnsafeCounter();
        int numThreads = 10;
        int incrementsPerThread = 1000;
        
        Thread[] threads = new Thread[numThreads];
        
        // Create threads that increment the counter
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    counter.increment();
                }
            });
        }
        
        // Start all threads
        long startTime = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        
        // Wait for all threads to complete
        for (Thread thread : threads) {
            thread.join();
        }
        long endTime = System.currentTimeMillis();
        
        int expectedValue = numThreads * incrementsPerThread;
        int actualValue = counter.getCount();
        
        System.out.println("Expected value: " + expectedValue);
        System.out.println("Actual value: " + actualValue);
        System.out.println("Data lost: " + (expectedValue - actualValue));
        System.out.println("Time taken: " + (endTime - startTime) + "ms");
        
        if (actualValue != expectedValue) {
            System.out.println("❌ Race condition occurred - data corruption detected!");
        } else {
            System.out.println("✅ No race condition detected (this time)");
        }
        System.out.println();
    }
    
    public static void main(String[] args) throws InterruptedException {
        // Run multiple times to see inconsistent results
        for (int i = 1; i <= 3; i++) {
            System.out.println("Run " + i + ":");
            demonstrateRaceCondition();
        }
    }
}

Synchronized Keyword

The synchronized keyword is Java's built-in mechanism for thread synchronization. It can be applied to methods or blocks of code.

Synchronized Methods

public class SynchronizedMethods {
    
    // Thread-safe counter using synchronized methods
    static class SafeCounter {
        private int count = 0;
        
        // Synchronized instance method
        public synchronized void increment() {
            count++; // Only one thread can execute this at a time
        }
        
        public synchronized void decrement() {
            count--;
        }
        
        public synchronized int getCount() {
            return count;
        }
        
        // Synchronized static method
        public static synchronized void printMessage(String message) {
            System.out.println("[" + Thread.currentThread().getName() + "] " + message);
        }
    }
    
    // Bank account example
    static class BankAccount {
        private double balance;
        private final String accountNumber;
        
        public BankAccount(String accountNumber, double initialBalance) {
            this.accountNumber = accountNumber;
            this.balance = initialBalance;
        }
        
        public synchronized void deposit(double amount) {
            if (amount > 0) {
                double oldBalance = balance;
                balance += amount;
                System.out.println("Deposited $" + amount + " to " + accountNumber + 
                                 ". Balance: $" + oldBalance + " -> $" + balance);
            }
        }
        
        public synchronized boolean withdraw(double amount) {
            if (amount > 0 && balance >= amount) {
                double oldBalance = balance;
                balance -= amount;
                System.out.println("Withdrew $" + amount + " from " + accountNumber + 
                                 ". Balance: $" + oldBalance + " -> $" + balance);
                return true;
            }
            System.out.println("Insufficient funds for withdrawal of $" + amount + 
                             " from " + accountNumber + ". Current balance: $" + balance);
            return false;
        }
        
        public synchronized double getBalance() {
            return balance;
        }
        
        public synchronized void transfer(BankAccount toAccount, double amount) {
            if (this.withdraw(amount)) {
                toAccount.deposit(amount);
                System.out.println("Transferred $" + amount + " from " + this.accountNumber + 
                                 " to " + toAccount.accountNumber);
            }
        }
    }
    
    public static void demonstrateSynchronizedMethods() throws InterruptedException {
        System.out.println("=== Synchronized Methods Demonstration ===");
        
        SafeCounter counter = new SafeCounter();
        int numThreads = 5;
        int operationsPerThread = 200;
        
        Thread[] threads = new Thread[numThreads];
        
        // Create threads that perform mixed operations
        for (int i = 0; i < numThreads; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < operationsPerThread; j++) {
                    if (j % 3 == 0) {
                        counter.decrement();
                    } else {
                        counter.increment();
                    }
                }
                SafeCounter.printMessage("Thread " + threadId + " completed");
            });
        }
        
        // Start all threads
        for (Thread thread : threads) {
            thread.start();
        }
        
        // Wait for completion
        for (Thread thread : threads) {
            thread.join();
        }
        
        int expectedValue = numThreads * operationsPerThread * 2 / 3; // More increments than decrements
        System.out.println("Final counter value: " + counter.getCount());
        System.out.println("Expected value: " + expectedValue);
        System.out.println();
    }
    
    public static void demonstrateBankAccount() throws InterruptedException {
        System.out.println("=== Bank Account Demonstration ===");
        
        BankAccount account1 = new BankAccount("ACC001", 1000.0);
        BankAccount account2 = new BankAccount("ACC002", 500.0);
        
        // Create multiple threads performing various operations
        Thread depositor = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                account1.deposit(100.0);
                try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        });
        
        Thread withdrawer = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                account1.withdraw(150.0);
                try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        });
        
        Thread transferer = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                account1.transfer(account2, 200.0);
                try { Thread.sleep(150); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        });
        
        depositor.start();
        withdrawer.start();
        transferer.start();
        
        depositor.join();
        withdrawer.join();
        transferer.join();
        
        System.out.println("Final balances:");
        System.out.println("Account 1: $" + account1.getBalance());
        System.out.println("Account 2: $" + account2.getBalance());
        System.out.println();
    }
    
    public static void main(String[] args) throws InterruptedException {
        demonstrateSynchronizedMethods();
        demonstrateBankAccount();
    }
}

Synchronized Blocks

Synchronized blocks provide more granular control over synchronization:

public class SynchronizedBlocks {
    
    // Multiple locks for fine-grained synchronization
    static class InventorySystem {
        private final Object stockLock = new Object();
        private final Object salesLock = new Object();
        
        private int stock = 100;
        private int totalSales = 0;
        private double revenue = 0.0;
        
        public void sellItem(String itemName, double price) {
            // Synchronized block for stock management
            synchronized (stockLock) {
                if (stock > 0) {
                    stock--;
                    System.out.println("Sold " + itemName + " for $" + price + 
                                     ". Remaining stock: " + stock);
                } else {
                    System.out.println("Cannot sell " + itemName + " - out of stock!");
                    return;
                }
            }
            
            // Separate synchronized block for sales tracking
            synchronized (salesLock) {
                totalSales++;
                revenue += price;
                System.out.println("Total sales: " + totalSales + ", Revenue: $" + 
                                 String.format("%.2f", revenue));
            }
        }
        
        public void restockItems(int quantity) {
            synchronized (stockLock) {
                stock += quantity;
                System.out.println("Restocked " + quantity + " items. Total stock: " + stock);
            }
        }
        
        public String getStatus() {
            // Multiple synchronized blocks to gather data
            int currentStock;
            synchronized (stockLock) {
                currentStock = stock;
            }
            
            int currentSales;
            double currentRevenue;
            synchronized (salesLock) {
                currentSales = totalSales;
                currentRevenue = revenue;
            }
            
            return String.format("Stock: %d, Sales: %d, Revenue: $%.2f", 
                               currentStock, currentSales, currentRevenue);
        }
    }
    
    // Class-level synchronization
    static class StaticSynchronization {
        private static int globalCounter = 0;
        private static final Object staticLock = new Object();
        
        public static void incrementGlobalCounter() {
            synchronized (StaticSynchronization.class) {
                globalCounter++;
                System.out.println("Global counter incremented to: " + globalCounter + 
                                 " by " + Thread.currentThread().getName());
            }
        }
        
        public static void decrementGlobalCounter() {
            synchronized (staticLock) {
                globalCounter--;
                System.out.println("Global counter decremented to: " + globalCounter + 
                                 " by " + Thread.currentThread().getName());
            }
        }
        
        public static int getGlobalCounter() {
            synchronized (StaticSynchronization.class) {
                return globalCounter;
            }
        }
    }
    
    // Producer-Consumer with synchronized blocks
    static class ProducerConsumer {
        private final Object lock = new Object();
        private final java.util.Queue<String> queue = new java.util.LinkedList<>();
        private final int maxSize = 5;
        
        public void produce(String item) throws InterruptedException {
            synchronized (lock) {
                while (queue.size() >= maxSize) {
                    System.out.println("Queue full, producer waiting...");
                    lock.wait(); // Release lock and wait
                }
                
                queue.offer(item);
                System.out.println("Produced: " + item + " (Queue size: " + queue.size() + ")");
                lock.notifyAll(); // Wake up waiting consumers
            }
        }
        
        public String consume() throws InterruptedException {
            synchronized (lock) {
                while (queue.isEmpty()) {
                    System.out.println("Queue empty, consumer waiting...");
                    lock.wait(); // Release lock and wait
                }
                
                String item = queue.poll();
                System.out.println("Consumed: " + item + " (Queue size: " + queue.size() + ")");
                lock.notifyAll(); // Wake up waiting producers
                return item;
            }
        }
    }
    
    public static void demonstrateInventorySystem() throws InterruptedException {
        System.out.println("=== Inventory System Demonstration ===");
        
        InventorySystem inventory = new InventorySystem();
        
        // Create seller threads
        Thread[] sellers = new Thread[3];
        for (int i = 0; i < sellers.length; i++) {
            final int sellerId = i + 1;
            sellers[i] = new Thread(() -> {
                for (int j = 1; j <= 5; j++) {
                    inventory.sellItem("Item-" + sellerId + "-" + j, 10.0 + j);
                    try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                }
            });
        }
        
        // Create restocking thread
        Thread restocker = new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            inventory.restockItems(20);
        });
        
        // Start all threads
        for (Thread seller : sellers) {
            seller.start();
        }
        restocker.start();
        
        // Wait for completion
        for (Thread seller : sellers) {
            seller.join();
        }
        restocker.join();
        
        System.out.println("Final status: " + inventory.getStatus());
        System.out.println();
    }
    
    public static void demonstrateProducerConsumer() throws InterruptedException {
        System.out.println("=== Producer-Consumer Demonstration ===");
        
        ProducerConsumer pc = new ProducerConsumer();
        
        // Producer thread
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 8; i++) {
                    pc.produce("Item-" + i);
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        // Consumer thread
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 1; i <= 8; i++) {
                    pc.consume();
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        producer.start();
        consumer.start();
        
        producer.join();
        consumer.join();
        
        System.out.println();
    }
    
    public static void main(String[] args) throws InterruptedException {
        demonstrateInventorySystem();
        demonstrateProducerConsumer();
        
        // Test static synchronization
        System.out.println("=== Static Synchronization ===");
        for (int i = 0; i < 3; i++) {
            new Thread(StaticSynchronization::incrementGlobalCounter).start();
            new Thread(StaticSynchronization::decrementGlobalCounter).start();
        }
        
        Thread.sleep(1000);
        System.out.println("Final global counter: " + StaticSynchronization.getGlobalCounter());
    }
}

Explicit Locks

Java provides explicit lock classes in java.util.concurrent.locks package for more advanced synchronization:

import java.util.concurrent.locks.*;
import java.util.concurrent.TimeUnit;

public class ExplicitLocks {
    
    // ReentrantLock example
    static class ReentrantLockExample {
        private final ReentrantLock lock = new ReentrantLock();
        private int count = 0;
        
        public void increment() {
            lock.lock();
            try {
                count++;
                System.out.println("Incremented to: " + count + " by " + 
                                 Thread.currentThread().getName());
            } finally {
                lock.unlock(); // Always unlock in finally block
            }
        }
        
        public boolean tryIncrement() {
            if (lock.tryLock()) {
                try {
                    count++;
                    System.out.println("Try-incremented to: " + count + " by " + 
                                     Thread.currentThread().getName());
                    return true;
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("Could not acquire lock for try-increment by " + 
                                 Thread.currentThread().getName());
                return false;
            }
        }
        
        public boolean tryIncrementWithTimeout() {
            try {
                if (lock.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        count++;
                        Thread.sleep(500); // Simulate some work
                        System.out.println("Timeout-incremented to: " + count + " by " + 
                                         Thread.currentThread().getName());
                        return true;
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println("Timeout while waiting for lock by " + 
                                     Thread.currentThread().getName());
                    return false;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        
        public int getCount() {
            lock.lock();
            try {
                return count;
            } finally {
                lock.unlock();
            }
        }
        
        public void printLockInfo() {
            System.out.println("Lock held by current thread: " + lock.isHeldByCurrentThread());
            System.out.println("Lock hold count: " + lock.getHoldCount());
            System.out.println("Queue length: " + lock.getQueueLength());
        }
    }
    
    // ReadWriteLock example
    static class ReadWriteLockExample {
        private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
        private final Lock readLock = rwLock.readLock();
        private final Lock writeLock = rwLock.writeLock();
        
        private String data = "Initial Data";
        private int readCount = 0;
        
        public String readData() {
            readLock.lock();
            try {
                readCount++;
                System.out.println("Reading data: '" + data + "' (Read count: " + readCount + 
                                 ") by " + Thread.currentThread().getName());
                Thread.sleep(1000); // Simulate read operation
                return data;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            } finally {
                readLock.unlock();
            }
        }
        
        public void writeData(String newData) {
            writeLock.lock();
            try {
                System.out.println("Writing data: '" + newData + "' by " + 
                                 Thread.currentThread().getName());
                Thread.sleep(2000); // Simulate write operation
                this.data = newData;
                System.out.println("Data written successfully by " + 
                                 Thread.currentThread().getName());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                writeLock.unlock();
            }
        }
        
        public void printLockInfo() {
            ReentrantReadWriteLock rrwl = (ReentrantReadWriteLock) rwLock;
            System.out.println("Read locks held: " + rrwl.getReadLockCount());
            System.out.println("Write lock held: " + rrwl.isWriteLocked());
        }
    }
    
    // Condition variables example
    static class ConditionExample {
        private final Lock lock = new ReentrantLock();
        private final Condition notEmpty = lock.newCondition();
        private final Condition notFull = lock.newCondition();
        
        private final String[] buffer = new String[3];
        private int count = 0;
        private int putIndex = 0;
        private int takeIndex = 0;
        
        public void put(String item) throws InterruptedException {
            lock.lock();
            try {
                while (count == buffer.length) {
                    System.out.println("Buffer full, producer waiting...");
                    notFull.await(); // Wait for space
                }
                
                buffer[putIndex] = item;
                putIndex = (putIndex + 1) % buffer.length;
                count++;
                
                System.out.println("Put: " + item + " (Buffer count: " + count + ")");
                notEmpty.signal(); // Signal that buffer is not empty
                
            } finally {
                lock.unlock();
            }
        }
        
        public String take() throws InterruptedException {
            lock.lock();
            try {
                while (count == 0) {
                    System.out.println("Buffer empty, consumer waiting...");
                    notEmpty.await(); // Wait for items
                }
                
                String item = buffer[takeIndex];
                buffer[takeIndex] = null;
                takeIndex = (takeIndex + 1) % buffer.length;
                count--;
                
                System.out.println("Take: " + item + " (Buffer count: " + count + ")");
                notFull.signal(); // Signal that buffer is not full
                
                return item;
                
            } finally {
                lock.unlock();
            }
        }
    }
    
    public static void demonstrateReentrantLock() throws InterruptedException {
        System.out.println("=== ReentrantLock Demonstration ===");
        
        ReentrantLockExample example = new ReentrantLockExample();
        
        // Test basic locking
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                example.increment();
                try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        });
        
        // Test try lock
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                example.tryIncrement();
                try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        });
        
        // Test try lock with timeout
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                example.tryIncrementWithTimeout();
            }
        });
        
        t1.start();
        t2.start();
        t3.start();
        
        t1.join();
        t2.join();
        t3.join();
        
        System.out.println("Final count: " + example.getCount());
        example.printLockInfo();
        System.out.println();
    }
    
    public static void demonstrateReadWriteLock() throws InterruptedException {
        System.out.println("=== ReadWriteLock Demonstration ===");
        
        ReadWriteLockExample example = new ReadWriteLockExample();
        
        // Multiple reader threads
        Thread[] readers = new Thread[3];
        for (int i = 0; i < readers.length; i++) {
            final int readerId = i + 1;
            readers[i] = new Thread(() -> {
                for (int j = 1; j <= 2; j++) {
                    example.readData();
                }
            }, "Reader-" + readerId);
        }
        
        // Writer thread
        Thread writer = new Thread(() -> {
            example.writeData("Updated Data");
        }, "Writer");
        
        // Start readers first
        for (Thread reader : readers) {
            reader.start();
        }
        
        Thread.sleep(500); // Let readers start
        writer.start(); // Start writer
        
        // Wait for completion
        for (Thread reader : readers) {
            reader.join();
        }
        writer.join();
        
        example.printLockInfo();
        System.out.println();
    }
    
    public static void demonstrateCondition() throws InterruptedException {
        System.out.println("=== Condition Variables Demonstration ===");
        
        ConditionExample example = new ConditionExample();
        
        // Producer thread
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    example.put("Item-" + i);
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer");
        
        // Consumer thread
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    example.take();
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer");
        
        producer.start();
        consumer.start();
        
        producer.join();
        consumer.join();
        
        System.out.println();
    }
    
    public static void main(String[] args) throws InterruptedException {
        demonstrateReentrantLock();
        demonstrateReadWriteLock();
        demonstrateCondition();
    }
}

Volatile and Atomic Operations

The volatile keyword and atomic classes provide low-level synchronization mechanisms:

import java.util.concurrent.atomic.*;

public class VolatileAndAtomic {
    
    // Volatile keyword example
    static class VolatileExample {
        private volatile boolean running = true;
        private volatile int counter = 0;
        
        public void start() {
            new Thread(() -> {
                int localCounter = 0;
                while (running) {
                    localCounter++;
                    if (localCounter % 1000000 == 0) {
                        counter = localCounter;
                        System.out.println("Worker thread counter: " + localCounter);
                    }
                }
                System.out.println("Worker thread stopped. Final counter: " + localCounter);
            }, "WorkerThread").start();
        }
        
        public void stop() {
            running = false; // This change will be immediately visible to the worker thread
            System.out.println("Stop signal sent");
        }
        
        public int getCounter() {
            return counter;
        }
    }
    
    // Atomic classes example
    static class AtomicExample {
        private final AtomicInteger atomicCounter = new AtomicInteger(0);
        private final AtomicLong atomicLong = new AtomicLong(0);
        private final AtomicBoolean atomicFlag = new AtomicBoolean(false);
        private final AtomicReference<String> atomicString = new AtomicReference<>("Initial");
        
        public void incrementCounter() {
            int newValue = atomicCounter.incrementAndGet();
            System.out.println("Atomic counter incremented to: " + newValue + 
                             " by " + Thread.currentThread().getName());
        }
        
        public boolean compareAndSwapCounter(int expected, int newValue) {
            boolean success = atomicCounter.compareAndSet(expected, newValue);
            System.out.println("CAS operation: expected=" + expected + ", new=" + newValue + 
                             ", success=" + success + " by " + Thread.currentThread().getName());
            return success;
        }
        
        public void updateLong() {
            long newValue = atomicLong.addAndGet(100);
            System.out.println("Atomic long updated to: " + newValue + 
                             " by " + Thread.currentThread().getName());
        }
        
        public void toggleFlag() {
            boolean oldValue = atomicFlag.getAndSet(!atomicFlag.get());
            System.out.println("Flag toggled from " + oldValue + " to " + atomicFlag.get() + 
                             " by " + Thread.currentThread().getName());
        }
        
        public void updateString(String newValue) {
            String oldValue = atomicString.getAndSet(newValue);
            System.out.println("String updated from '" + oldValue + "' to '" + newValue + 
                             "' by " + Thread.currentThread().getName());
        }
        
        public void printValues() {
            System.out.println("Current values:");
            System.out.println("  Counter: " + atomicCounter.get());
            System.out.println("  Long: " + atomicLong.get());
            System.out.println("  Flag: " + atomicFlag.get());
            System.out.println("  String: " + atomicString.get());
        }
    }
    
    // Compare performance: synchronized vs atomic
    static class PerformanceComparison {
        private int synchronizedCounter = 0;
        private final AtomicInteger atomicCounter = new AtomicInteger(0);
        
        public synchronized void incrementSynchronized() {
            synchronizedCounter++;
        }
        
        public void incrementAtomic() {
            atomicCounter.incrementAndGet();
        }
        
        public int getSynchronizedCounter() {
            return synchronizedCounter;
        }
        
        public int getAtomicCounter() {
            return atomicCounter.get();
        }
        
        public void runPerformanceTest() throws InterruptedException {
            int numThreads = 4;
            int incrementsPerThread = 250000;
            
            // Test synchronized increment
            Thread[] syncThreads = new Thread[numThreads];
            long startTime = System.currentTimeMillis();
            
            for (int i = 0; i < numThreads; i++) {
                syncThreads[i] = new Thread(() -> {
                    for (int j = 0; j < incrementsPerThread; j++) {
                        incrementSynchronized();
                    }
                });
            }
            
            for (Thread thread : syncThreads) {
                thread.start();
            }
            for (Thread thread : syncThreads) {
                thread.join();
            }
            
            long syncTime = System.currentTimeMillis() - startTime;
            
            // Test atomic increment
            Thread[] atomicThreads = new Thread[numThreads];
            startTime = System.currentTimeMillis();
            
            for (int i = 0; i < numThreads; i++) {
                atomicThreads[i] = new Thread(() -> {
                    for (int j = 0; j < incrementsPerThread; j++) {
                        incrementAtomic();
                    }
                });
            }
            
            for (Thread thread : atomicThreads) {
                thread.start();
            }
            for (Thread thread : atomicThreads) {
                thread.join();
            }
            
            long atomicTime = System.currentTimeMillis() - startTime;
            
            System.out.println("Performance Comparison:");
            System.out.println("Synchronized counter: " + getSynchronizedCounter() + 
                             " (Time: " + syncTime + "ms)");
            System.out.println("Atomic counter: " + getAtomicCounter() + 
                             " (Time: " + atomicTime + "ms)");
            System.out.println("Atomic is " + (syncTime > atomicTime ? "faster" : "slower") + 
                             " by " + Math.abs(syncTime - atomicTime) + "ms");
        }
    }
    
    public static void demonstrateVolatile() throws InterruptedException {
        System.out.println("=== Volatile Demonstration ===");
        
        VolatileExample example = new VolatileExample();
        example.start();
        
        Thread.sleep(2000); // Let it run for 2 seconds
        
        System.out.println("Main thread counter: " + example.getCounter());
        example.stop();
        
        Thread.sleep(100); // Give time for worker thread to stop
        System.out.println();
    }
    
    public static void demonstrateAtomic() throws InterruptedException {
        System.out.println("=== Atomic Classes Demonstration ===");
        
        AtomicExample example = new AtomicExample();
        
        // Create multiple threads to test atomic operations
        Thread[] threads = new Thread[3];
        
        for (int i = 0; i < threads.length; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                example.incrementCounter();
                example.updateLong();
                example.toggleFlag();
                example.updateString("Value-" + threadId);
                
                // Test compare-and-swap
                example.compareAndSwapCounter(threadId + 1, threadId * 10);
            }, "Thread-" + i);
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        
        example.printValues();
        System.out.println();
    }
    
    public static void main(String[] args) throws InterruptedException {
        demonstrateVolatile();
        demonstrateAtomic();
        
        // Performance comparison
        System.out.println("=== Performance Comparison ===");
        PerformanceComparison comparison = new PerformanceComparison();
        comparison.runPerformanceTest();
    }
}

Deadlock Prevention and Detection

Deadlocks are a critical issue in concurrent programming that must be prevented:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockExample {
    
    // Classic deadlock scenario
    static class DeadlockDemo {
        private final Object lock1 = new Object();
        private final Object lock2 = new Object();
        
        public void method1() {
            synchronized (lock1) {
                System.out.println(Thread.currentThread().getName() + " acquired lock1");
                
                try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                
                System.out.println(Thread.currentThread().getName() + " waiting for lock2");
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock2");
                }
            }
        }
        
        public void method2() {
            synchronized (lock2) {
                System.out.println(Thread.currentThread().getName() + " acquired lock2");
                
                try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                
                System.out.println(Thread.currentThread().getName() + " waiting for lock1");
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock1");
                }
            }
        }
    }
    
    // Deadlock prevention using lock ordering
    static class DeadlockPrevention {
        private final Object lock1 = new Object();
        private final Object lock2 = new Object();
        
        // Always acquire locks in the same order
        public void method1() {
            synchronized (lock1) {
                System.out.println(Thread.currentThread().getName() + " acquired lock1");
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock2");
                    // Do work
                }
            }
        }
        
        public void method2() {
            synchronized (lock1) { // Same order as method1
                System.out.println(Thread.currentThread().getName() + " acquired lock1");
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock2");
                    // Do work
                }
            }
        }
    }
    
    // Deadlock prevention using timeouts
    static class TimeoutPrevention {
        private final Lock lock1 = new ReentrantLock();
        private final Lock lock2 = new ReentrantLock();
        
        public boolean method1() {
            boolean acquired1 = false, acquired2 = false;
            
            try {
                acquired1 = lock1.tryLock(1, TimeUnit.SECONDS);
                if (acquired1) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock1");
                    
                    acquired2 = lock2.tryLock(1, TimeUnit.SECONDS);
                    if (acquired2) {
                        System.out.println(Thread.currentThread().getName() + " acquired lock2");
                        // Do work
                        return true;
                    } else {
                        System.out.println(Thread.currentThread().getName() + " failed to acquire lock2");
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " failed to acquire lock1");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                if (acquired2) lock2.unlock();
                if (acquired1) lock1.unlock();
            }
            
            return false;
        }
        
        public boolean method2() {
            boolean acquired1 = false, acquired2 = false;
            
            try {
                acquired2 = lock2.tryLock(1, TimeUnit.SECONDS);
                if (acquired2) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock2");
                    
                    acquired1 = lock1.tryLock(1, TimeUnit.SECONDS);
                    if (acquired1) {
                        System.out.println(Thread.currentThread().getName() + " acquired lock1");
                        // Do work
                        return true;
                    } else {
                        System.out.println(Thread.currentThread().getName() + " failed to acquire lock1");
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " failed to acquire lock2");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                if (acquired1) lock1.unlock();
                if (acquired2) lock2.unlock();
            }
            
            return false;
        }
    }
    
    public static void demonstrateDeadlock() throws InterruptedException {
        System.out.println("=== Deadlock Demonstration ===");
        System.out.println("Creating deadlock scenario...");
        
        DeadlockDemo demo = new DeadlockDemo();
        
        Thread t1 = new Thread(() -> demo.method1(), "Thread-1");
        Thread t2 = new Thread(() -> demo.method2(), "Thread-2");
        
        t1.start();
        t2.start();
        
        // Wait for a few seconds to see if deadlock occurs
        Thread.sleep(3000);
        
        if (t1.isAlive() || t2.isAlive()) {
            System.out.println("❌ Deadlock detected! Threads are still running.");
            t1.interrupt();
            t2.interrupt();
        } else {
            System.out.println("✅ No deadlock occurred.");
        }
        
        System.out.println();
    }
    
    public static void demonstrateDeadlockPrevention() throws InterruptedException {
        System.out.println("=== Deadlock Prevention with Lock Ordering ===");
        
        DeadlockPrevention prevention = new DeadlockPrevention();
        
        Thread t1 = new Thread(() -> prevention.method1(), "Thread-1");
        Thread t2 = new Thread(() -> prevention.method2(), "Thread-2");
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        System.out.println("✅ No deadlock - lock ordering prevented it.");
        System.out.println();
    }
    
    public static void demonstrateTimeoutPrevention() throws InterruptedException {
        System.out.println("=== Deadlock Prevention with Timeouts ===");
        
        TimeoutPrevention timeout = new TimeoutPrevention();
        
        Thread t1 = new Thread(() -> {
            boolean success = timeout.method1();
            System.out.println("Thread-1 " + (success ? "succeeded" : "failed"));
        }, "Thread-1");
        
        Thread t2 = new Thread(() -> {
            boolean success = timeout.method2();
            System.out.println("Thread-2 " + (success ? "succeeded" : "failed"));
        }, "Thread-2");
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        System.out.println("✅ Timeouts prevented deadlock.");
        System.out.println();
    }
    
    public static void main(String[] args) throws InterruptedException {
        demonstrateDeadlock();
        demonstrateDeadlockPrevention();
        demonstrateTimeoutPrevention();
    }
}

Summary

Java synchronization provides essential mechanisms for thread safety:

Synchronization Mechanisms:

  • synchronized keyword: Built-in mutex locks for methods and blocks
  • Explicit Locks: ReentrantLock, ReadWriteLock for advanced control
  • volatile keyword: Ensures memory visibility without locking
  • Atomic Classes: Lock-free thread-safe operations

Key Concepts:

  • Mutual Exclusion: Only one thread can access synchronized code
  • Memory Visibility: Changes are visible across threads
  • Atomicity: Operations complete without interruption
  • Reentrance: Same thread can acquire the same lock multiple times

Best Practices:

  • Minimize Lock Scope: Keep synchronized blocks as small as possible
  • Consistent Lock Ordering: Prevent deadlocks by acquiring locks in order
  • Use Timeouts: Avoid indefinite blocking with tryLock()
  • Prefer Read/Write Locks: When reads greatly outnumber writes
  • Choose Atomic Classes: For simple operations on single variables
  • Avoid Nested Locks: Reduce deadlock risk

Performance Considerations:

  • Lock Contention: High contention reduces performance
  • Context Switching: Blocking threads causes overhead
  • Cache Effects: Synchronized access can cause cache misses
  • Lock-Free Alternatives: Atomic operations often perform better

Common Pitfalls:

  • Forgetting to Unlock: Always use try-finally with explicit locks
  • Deadlocks: Circular waiting for locks
  • Race Conditions: Insufficient synchronization
  • Over-Synchronization: Unnecessary locking hurts performance

Understanding synchronization is crucial for building correct, efficient concurrent Java applications that can safely share data between multiple threads.