Skip to content
IRC-CodingIRC-Coding
Java multithreadingThread synchronizationWait notify patternConcurrent locksSemaphoresThread safetyDeadlock preventionPerformance optimization

Java Multithreading: Threads, Locks & Synchronization

Master Java multithreading with threads, synchronization, wait/notify, locks, semaphores, and thread safety patterns.

S

schutzgeist

16 min read
Java Multithreading: Threads, Locks & Synchronization

Multithreading in Java: Threads, Synchronisation, Wait/Notify & Concurrent Locks

This is a comprehensive guide to Java Multithreading – covering threads, synchronisation, wait/notify, concurrent locks, and semaphores with practical examples.

In a Nutshell

Multithreading enables parallel task execution. Synchronisation ensures consistent data access, while concurrent locks provide modern mechanisms for controlling thread behaviour.

Core Concepts

Multithreading is the ability of a program to execute multiple threads concurrently. Each thread has its own stack but shares heap memory with other threads.

Key Fundamentals:

Thread Lifecycle

  • NEW: Thread created but not yet started
  • RUNNABLE: Ready for execution (running or ready state)
  • BLOCKED: Waiting to acquire a monitor lock
  • WAITING: Waiting indefinitely for a condition
  • TIMED_WAITING: Waiting with a time limit
  • TERMINATED: Thread has finished

Synchronisation Mechanisms

  • synchronized: Monitor-based synchronisation
  • wait()/notify(): Inter-thread communication
  • ReentrantLock: Flexible locking mechanism
  • ReadWriteLock: Separate read and write locks
  • Semaphore: Counter-based access control
  • CountDownLatch: Wait for multiple threads to complete
  • CyclicBarrier: Synchronisation point for threads

Thread Safety

  • Immutable Objects: Inherently thread-safe
  • ThreadLocal: Thread-specific data storage
  • Volatile: Visibility guarantees for variables
  • Atomic Classes: Lock-free operations

Essential Topics

  • Threads: Lightweight processes with separate stacks
  • Synchronisation: Protecting shared resources
  • Monitor: Object-based synchronisation mechanism
  • wait/notify: Inter-thread communication patterns
  • Deadlock: Mutual blocking situation
  • Race Condition: Uncontrolled access to shared data
  • Volatile: Guarantees visibility across threads
  • Performance: Critical for scalable, concurrent applications

Core Components

  1. Thread Management: Creation, control, lifecycle
  2. Synchronisation: synchronized blocks, locks, semaphores
  3. Inter-Thread Communication: wait/notify, blocking queues
  4. Concurrent Collections: Thread-safe data structures
  5. Thread Pools: ExecutorService, ThreadPoolExecutor
  6. Thread Safety: Immutable objects, volatile fields, atomic classes
  7. Performance: Lock granularity, contention reduction
  8. Debugging: Thread dumps, race conditions, deadlock detection

Practical Examples

1. Basic Thread Operations

public class ThreadGrundlagen {
    
    // Thread via inheritance from Thread
    static class MeinThread extends Thread {
        private String name;
        
        public MeinThread(String name) {
            this.name = name;
        }
        
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                System.out.println(name + " - Zählung: " + i);
                try {
                    Thread.sleep(500); // 500ms pause
                } catch (InterruptedException e) {
                    System.out.println(name + " wurde unterbrochen");
                    return;
                }
            }
            System.out.println(name + " beendet");
        }
    }
    
    // Thread via Runnable interface
    static class MeinRunnable implements Runnable {
        private String name;
        
        public MeinRunnable(String name) {
            this.name = name;
        }
        
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                System.out.println(name + " - Arbeit: " + i);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    System.out.println(name + " unterbrochen");
                    return;
                }
            }
            System.out.println(name + " fertig");
        }
    }
    
    // Lambda expression as Runnable
    static void lambdaThreadDemo() {
        Thread lambdaThread = new Thread(() -> {
            for (int i = 1; i <= 3; i++) {
                System.out.println("Lambda Thread - Schritt " + i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return;
                }
            }
        });
        
        lambdaThread.start();
    }
    
    public static void main(String[] args) {
        System.out.println("=== Thread-Grundlagen ===");
        
        // Thread via inheritance
        MeinThread thread1 = new MeinThread("Thread-1");
        thread1.start();
        
        // Thread via Runnable
        Thread thread2 = new Thread(new MeinRunnable("Runnable-1"));
        thread2.start();
        
        // Lambda thread
        lambdaThreadDemo();
        
        // Wait for threads to complete
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Hauptthread beendet");
    }
}

2. Synchronisation with synchronized

public class SynchronisationDemo {
    
    // Shared resource
    static class Zaehler {
        private int wert = 0;
        
        // Synchronised method
        public synchronized void erhoehen() {
            wert++;
            System.out.println(Thread.currentThread().getName() + 
                             " erhöht auf: " + wert);
        }
        
        // Synchronised block
        public void verringern() {
            synchronized(this) {
                wert--;
                System.out.println(Thread.currentThread().getName() + 
                                 " verringert auf: " + wert);
            }
        }
        
        public synchronized int getWert() {
            return wert;
        }
    }
    
    // Producer-Consumer with wait/notify
    static class Warenlager {
        private final int[] lager = new int[5];
        private int index = 0;
        
        public synchronized void einlagern(int ware) throws InterruptedException {
            // Wait if warehouse is full
            while (index >= lager.length) {
                System.out.println("Lager voll - Producer wartet");
                wait();
            }
            
            lager[index] = ware;
            index++;
            System.out.println(Thread.currentThread().getName() + 
                             " eingelagert: " + ware);
            
            // Notify consumer
            notifyAll();
        }
        
        public synchronized int auslagern() throws InterruptedException {
            // Wait if warehouse is empty
            while (index <= 0) {
                System.out.println("Lager leer - Consumer wartet");
                wait();
            }
            
            index--;
            int ware = lager[index];
            System.out.println(Thread.currentThread().getName() + 
                             " ausgelagert: " + ware);
            
            // Notify producer
            notifyAll();
            
            return ware;
        }
    }
    
    static class Producer implements Runnable {
        private Warenlager lager;
        
        public Producer(Warenlager lager) {
            this.lager = lager;
        }
        
        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    lager.einlagern(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    static class Consumer implements Runnable {
        private Warenlager lager;
        
        public Consumer(Warenlager lager) {
            this.lager = lager;
        }
        
        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    lager.auslagern();
                    Thread.sleep(150);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== Synchronisation Demo ===");
        
        // Basic synchronisation
        Zaehler zaehler = new Zaehler();
        
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    zaehler.erhoehen();
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            });
            threads[i].setName("Thread-" + i);
        }
        
        // Start threads
        for (Thread t : threads) {
            t.start();
        }
        
        // Wait for threads to complete
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Endwert: " + zaehler.getWert());
        
        // Producer-Consumer demo
        System.out.println("\n=== Producer-Consumer Demo ===");
        Warenlager lager = new Warenlager();
        
        Thread producer = new Thread(new Producer(lager), "Producer");
        Thread consumer = new Thread(new Consumer(lager), "Consumer");
        
        producer.start();
        consumer.start();
        
        try {
            producer.join();
            consumer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3. Concurrent Locks and Modern Synchronisation

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

public class ConcurrentLocksDemo {
    
    // ReentrantLock Beispiel
    static class Bankkonto {
        private double kontostand;
        private final ReentrantLock lock = new ReentrantLock();
        
        public Bankkonto(double startbetrag) {
            this.kontostand = startbetrag;
        }
        
        public void einzahlen(double betrag) {
            lock.lock();
            try {
                double alterStand = kontostand;
                Thread.sleep(50); // Simuliere Verarbeitung
                kontostand = alterStand + betrag;
                System.out.println(Thread.currentThread().getName() + 
                                 " eingezahlt: " + betrag + 
                                 ", neuer Stand: " + kontostand);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
        
        public boolean abheben(double betrag) {
            lock.lock();
            try {
                if (kontostand >= betrag) {
                    double alterStand = kontostand;
                    Thread.sleep(50);
                    kontostand = alterStand - betrag;
                    System.out.println(Thread.currentThread().getName() + 
                                     " abgehoben: " + betrag + 
                                     ", neuer Stand: " + kontostand);
                    return true;
                } else {
                    System.out.println(Thread.currentThread().getName() + 
                                     " Konnte nicht abheben: " + betrag + 
                                     " (Stand: " + kontostand + ")");
                    return false;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } finally {
                lock.unlock();
            }
        }
        
        public double getKontostand() {
            lock.lock();
            try {
                return kontostand;
            } finally {
                lock.unlock();
            }
        }
    }
    
    // ReadWriteLock Beispiel
    static class ThreadSafeList {
        private final List<String> liste = new ArrayList<>();
        private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
        private final Lock readLock = rwLock.readLock();
        private final Lock writeLock = rwLock.writeLock();
        
        public void add(String element) {
            writeLock.lock();
            try {
                liste.add(element);
                System.out.println(Thread.currentThread().getName() + 
                                 " hinzugefügt: " + element);
            } finally {
                writeLock.unlock();
            }
        }
        
        public String get(int index) {
            readLock.lock();
            try {
                return liste.get(index);
            } finally {
                readLock.unlock();
            }
        }
        
        public List<String> getAll() {
            readLock.lock();
            try {
                return new ArrayList<>(liste); // Kopie zurückgeben
            } finally {
                readLock.unlock();
            }
        }
        
        public int size() {
            readLock.lock();
            try {
                return liste.size();
            } finally {
                readLock.unlock();
            }
        }
    }
    
    // Semaphore Beispiel
    static class RessourcenPool {
        private final Semaphore semaphore;
        private final List<String> ressourcen;
        
        public RessourcenPool(int maxRessourcen) {
            semaphore = new Semaphore(maxRessourcen);
            ressourcen = new ArrayList<>();
            for (int i = 1; i <= maxRessourcen; i++) {
                ressourcen.add("Ressource-" + i);
            }
        }
        
        public String acquire() throws InterruptedException {
            semaphore.acquire();
            
            synchronized(ressourcen) {
                if (!ressourcen.isEmpty()) {
                    String ressource = ressourcen.remove(0);
                    System.out.println(Thread.currentThread().getName() + 
                                     " acquired: " + ressource);
                    return ressource;
                }
            }
            
            semaphore.release();
            return null;
        }
        
        public void release(String ressource) {
            synchronized(ressourcen) {
                ressourcen.add(ressource);
                System.out.println(Thread.currentThread().getName() + 
                                 " released: " + ressource);
            }
            semaphore.release();
        }
    }
    
    // CountDownLatch Beispiel
    static class Worker implements Runnable {
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;
        private final int workerId;
        
        public Worker(CountDownLatch startSignal, CountDownLatch doneSignal, int workerId) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
            this.workerId = workerId;
        }
        
        @Override
        public void run() {
            try {
                // Warten auf Startsignal
                System.out.println("Worker " + workerId + " bereit");
                startSignal.await();
                
                // Arbeit ausführen
                System.out.println("Worker " + workerId + " arbeitet");
                Thread.sleep((long) (Math.random() * 1000));
                
                System.out.println("Worker " + workerId + " fertig");
                
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                doneSignal.countDown();
            }
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== Concurrent Locks Demo ===");
        
        // ReentrantLock Demo
        Bankkonto konto = new Bankkonto(1000.0);
        
        Thread[] bankThreads = new Thread[4];
        for (int i = 0; i < bankThreads.length; i++) {
            final int threadId = i;
            bankThreads[i] = new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    if (threadId % 2 == 0) {
                        konto.einzahlen(100);
                    } else {
                        konto.abheben(50);
                    }
                }
            }, "BankThread-" + i);
        }
        
        for (Thread t : bankThreads) {
            t.start();
        }
        
        for (Thread t : bankThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Endkontostand: " + konto.getKontostand());
        
        // ReadWriteLock Demo
        System.out.println("\n=== ReadWriteLock Demo ===");
        ThreadSafeList liste = new ThreadSafeList();
        
        // Writer Thread
        Thread writer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                liste.add("Element-" + i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return;
                }
            }
        }, "Writer");
        
        // Reader Threads
        Thread[] readers = new Thread[3];
        for (int i = 0; i < readers.length; i++) {
            readers[i] = new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    List<String> alle = liste.getAll();
                    System.out.println(Thread.currentThread().getName() + 
                                     " gelesen: " + alle.size() + " Elemente");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }, "Reader-" + i);
        }
        
        writer.start();
        for (Thread reader : readers) {
            reader.start();
        }
        
        try {
            writer.join();
            for (Thread reader : readers) {
                reader.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // Semaphore Demo
        System.out.println("\n=== Semaphore Demo ===");
        RessourcenPool pool = new RessourcenPool(2);
        
        Thread[] poolThreads = new Thread[5];
        for (int i = 0; i < poolThreads.length; i++) {
            poolThreads[i] = new Thread(() -> {
                try {
                    String ressource = pool.acquire();
                    if (ressource != null) {
                        Thread.sleep(1000); // Ressource nutzen
                        pool.release(ressource);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "PoolThread-" + i);
        }
        
        for (Thread t : poolThreads) {
            t.start();
        }
        
        for (Thread t : poolThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // CountDownLatch Demo
        System.out.println("\n=== CountDownLatch Demo ===");
        int workerCount = 3;
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(workerCount);
        
        for (int i = 1; i <= workerCount; i++) {
            new Thread(new Worker(startSignal, doneSignal, i)).start();
        }
        
        try {
            Thread.sleep(1000);
            System.out.println("Alle Worker bereit - Startsignal!");
            startSignal.countDown();
            
            doneSignal.await();
            System.out.println("Alle Worker fertig!");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

This section explores the core synchronisation mechanisms available in Java’s concurrent package. Instead of relying solely on the synchronized keyword, modern Java provides more sophisticated tools for controlling access to shared resources.

ReentrantLock gives you explicit control over locking. A thread can acquire the same lock multiple times without deadlocking—useful when methods need to call other synchronized methods. In the example above, the bank account class uses ReentrantLock to ensure that deposit and withdrawal operations remain atomic. Notice how the try-finally pattern guarantees that the lock is always released, even if an exception occurs.

ReadWriteLock is particularly valuable when your data structure experiences far more reads than writes. Multiple threads can hold the read lock simultaneously, significantly boosting throughput for read-heavy workloads. Writers, however, get exclusive access. The ThreadSafeList demonstrates this pattern: multiple reader threads can access the list concurrently while a single writer updates it periodically.

Semaphore controls access to a limited pool of resources. If you need to restrict how many threads can use a particular service or resource at the same time, a semaphore is your answer. The RessourcenPool example shows how to enforce a maximum of two concurrent resource acquisitions across five competing threads.

CountDownLatch synchronises one or more threads by having them wait until a countdown reaches zero. This is excellent for coordinating the start or completion of work. In the example, all worker threads wait for the latch to countdown before they begin processing, ensuring they all start in unison.

These primitives compose well together and enable cleaner, more readable concurrent code than nested synchronized blocks. They also provide better performance characteristics and more flexibility for different concurrency patterns.

4. Thread-Safe Data Structures and Atomic Classes

import java.util.concurrent.atomic.*;
import java.util.concurrent.*;

public class ThreadSafeCollections {
    
    // Atomic Classes Demo
    static class AtomicCounter {
        private final AtomicInteger counter = new AtomicInteger(0);
        private final AtomicLong longCounter = new AtomicLong(0);
        private final AtomicBoolean flag = new AtomicBoolean(false);
        private final AtomicReference<String> message = new AtomicReference<>("");
        
        public void increment() {
            int oldValue = counter.getAndIncrement();
            System.out.println(Thread.currentThread().getName() + 
                             " increment: " + oldValue + " -> " + counter.get());
        }
        
        public void add(long value) {
            long oldValue = longCounter.getAndAdd(value);
            System.out.println(Thread.currentThread().getName() + 
                             " add: " + oldValue + " + " + value + " -> " + longCounter.get());
        }
        
        public void toggleFlag() {
            boolean oldValue = flag.getAndSet(!flag.get());
            System.out.println(Thread.currentThread().getName() + 
                             " toggle: " + oldValue + " -> " + flag.get());
        }
        
        public void updateMessage(String newMessage) {
            String oldMessage = message.getAndSet(newMessage);
            System.out.println(Thread.currentThread().getName() + 
                             " update: '" + oldMessage + "' -> '" + newMessage + "'");
        }
        
        public int getCounter() { return counter.get(); }
        public long getLongCounter() { return longCounter.get(); }
        public boolean getFlag() { return flag.get(); }
        public String getMessage() { return message.get(); }
    }
    
    // Concurrent Collections Demo
    static class ConcurrentCollectionsDemo {
        
        public static void hashMapDemo() {
            System.out.println("=== ConcurrentHashMap Demo ===");
            ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
            
            // Writer Threads
            Thread[] writers = new Thread[3];
            for (int i = 0; i < writers.length; i++) {
                final int threadId = i;
                writers[i] = new Thread(() -> {
                    for (int j = 0; j < 5; j++) {
                        String key = "Key-" + threadId + "-" + j;
                        map.put(key, threadId * 100 + j);
                        System.out.println(Thread.currentThread().getName() + 
                                         " put: " + key);
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }, "Writer-" + i);
            }
            
            // Reader Thread
            Thread reader = new Thread(() -> {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + 
                                     " size: " + map.size());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }, "Reader");
            
            // Start all threads
            reader.start();
            for (Thread writer : writers) {
                writer.start();
            }
            
            // Wait for threads to complete
            try {
                for (Thread writer : writers) {
                    writer.join();
                }
                reader.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("Final map size: " + map.size());
        }
        
        public static void blockingQueueDemo() {
            System.out.println("\n=== BlockingQueue Demo ===");
            BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
            
            // Producer
            Thread producer = new Thread(() -> {
                try {
                    for (int i = 1; i <= 10; i++) {
                        String item = "Item-" + i;
                        queue.put(item);
                        System.out.println("Producer put: " + item + 
                                         " (queue size: " + queue.size() + ")");
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "Producer");
            
            // Consumer
            Thread consumer = new Thread(() -> {
                try {
                    for (int i = 1; i <= 10; i++) {
                        String item = queue.take();
                        System.out.println("Consumer take: " + item + 
                                         " (queue size: " + queue.size() + ")");
                        Thread.sleep(300);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "Consumer");
            
            producer.start();
            consumer.start();
            
            try {
                producer.join();
                consumer.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    // ThreadLocal Demo
    static class ThreadLocalDemo {
        private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 100);
        private static ThreadLocal<String> threadLocalName = new ThreadLocal<>();
        
        public static void demo() {
            System.out.println("=== ThreadLocal Demo ===");
            
            Thread[] threads = new Thread[3];
            for (int i = 0; i < threads.length; i++) {
                final int threadId = i;
                threads[i] = new Thread(() -> {
                    threadLocalName.set("Thread-" + threadId);
                    
                    for (int j = 0; j < 3; j++) {
                        int value = threadLocalValue.get();
                        String name = threadLocalName.get();
                        
                        System.out.println(name + " value: " + value);
                        
                        // Modify ThreadLocal value
                        threadLocalValue.set(value + threadId);
                        
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                    
                    // Clean up ThreadLocal
                    threadLocalName.remove();
                    threadLocalValue.remove();
                }, "Thread-" + i);
            }
            
            for (Thread t : threads) {
                t.start();
            }
            
            for (Thread t : threads) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String[] args) {
        // Atomic Classes Demo
        System.out.println("=== Atomic Classes Demo ===");
        AtomicCounter counter = new AtomicCounter();
        
        Thread[] atomicThreads = new Thread[4];
        for (int i = 0; i < atomicThreads.length; i++) {
            final int threadId = i;
            atomicThreads[i] = new Thread(() -> {
                counter.increment();
                counter.add(threadId * 10);
                counter.toggleFlag();
                counter.updateMessage("Message from Thread-" + threadId);
            }, "AtomicThread-" + i);
        }
        
        for (Thread t : atomicThreads) {
            t.start();
        }
        
        for (Thread t : atomicThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Final values:");
        System.out.println("Counter: " + counter.getCounter());
        System.out.println("LongCounter: " + counter.getLongCounter());
        System.out.println("Flag: " + counter.getFlag());
        System.out.println("Message: " + counter.getMessage());
        
        // Concurrent Collections Demo
        ConcurrentCollectionsDemo.hashMapDemo();
        ConcurrentCollectionsDemo.blockingQueueDemo();
        
        // ThreadLocal Demo
        ThreadLocalDemo.demo();
    }
}

Thread Pools and ExecutorService

ThreadPoolExecutor

// Fixed Thread Pool
ExecutorService fixedPool = Executors.newFixedThreadPool(4);

// Cached Thread Pool
ExecutorService cachedPool = Executors.newCachedThreadPool();

// Single Thread Executor
ExecutorService singlePool = Executors.newSingleThreadExecutor();

// Scheduled Thread Pool
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);

// Execute tasks
Future<String> future = fixedPool.submit(() -> {
    Thread.sleep(1000);
    return "Ergebnis";
});

// Scheduled Tasks
scheduledPool.scheduleAtFixedRate(() -> {
    System.out.println("Periodische Aufgabe");
}, 0, 1, TimeUnit.SECONDS);

// Shut down pools
fixedPool.shutdown();
scheduledPool.shutdown();

Deadlock Prevention

Deadlock Conditions

  1. Mutual Exclusion: A resource can only be used by one thread at a time
  2. Hold and Wait: A thread holds resources while waiting for others
  3. No Preemption: Resources cannot be forcibly released
  4. Circular Wait: Threads wait in a circular chain

Avoidance Strategies

// Lock Ordering - always acquire locks in the same order
public void transfer(Account from, Account to, double amount) {
    // Synchronize accounts in consistent order
    Account first = from.getId() < to.getId() ? from : to;
    Account second = from.getId() < to.getId() ? to : from;
    
    synchronized(first) {
        synchronized(second) {
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}

// TryLock with Timeout
public boolean transferWithTryLock(Account from, Account to, double amount) {
    while (true) {
        try {
            if (from.getLock().tryLock(1, TimeUnit.SECONDS)) {
                try {
                    if (to.getLock().tryLock(1, TimeUnit.SECONDS)) {
                        try {
                            from.withdraw(amount);
                            to.deposit(amount);
                            return true;
                        } finally {
                            to.getLock().unlock();
                        }
                    }
                } finally {
                    from.getLock().unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
        // Brief pause before retry
        Thread.sleep(100);
    }
}

Performance Optimization

Lock Granularity

// Coarse-grained synchronization (poor)
public synchronized void addElement(Object element) {
    // Entire operation is locked
    list.add(element);
    size++;
}

// Fine-grained synchronization (better)
public void addElement(Object element) {
    synchronized(list) {
        list.add(element);
    }
    synchronized(this) {
        size++;
    }
}

Volatile vs Synchronized

// Volatile for simple visibility
private volatile boolean running = true;

public void stop() {
    running = false; // Visible to all threads
}

public void run() {
    while (running) {
        // Do work
    }
}

// Synchronized for complex operations
private int counter = 0;

public synchronized void increment() {
    counter++; // Atomic operation
}

Advantages and Disadvantages

Benefits of Multithreading

  • Performance: Parallel execution on multi-core systems
  • Responsiveness: UI stays reactive during long operations
  • Resource utilization: Better use of system resources
  • Scalability: Work can be distributed across threads

Drawbacks

  • Complexity: Synchronization is error-prone
  • Debugging: Race conditions are difficult to reproduce
  • Overhead: Thread creation and context switching consume time
  • Resources: Higher memory and CPU consumption

Common Exam Questions

  1. What’s the difference between wait() and sleep()? wait() releases the lock, sleep() keeps it. wait() requires synchronized, sleep() does not.

  2. Explain deadlock and how to prevent it. Deadlock is mutual blocking between threads. Prevention through lock ordering or tryLock with timeouts.

  3. When do you use volatile instead of synchronized? volatile for simple variable visibility, synchronized for complex operations.

  4. What’s the difference between Runnable and Callable? Runnable has no return value, Callable returns a result and can throw exceptions.

Key Resources

  1. https://docs.oracle.com/javase/tutorial/essential/concurrency/
  2. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html
  3. https://www.baeldung.com/java-concurrency
Back to Blog
Share:

Related Posts