Skip to content
IRC-CodingIRC-Coding
Multithreading JavaПотоки синхронизацияWait NotifyConcurrent LocksSemaphoresThread Safety

Multithreading в Java: потоки и синхронизация

Multithreading в Java с потоками, синхронизацией, wait/notify, concurrent locks, semaphores и оптимизацией производительности.

S

schutzgeist

15 min read
Multithreading в Java: потоки и синхронизация

Многопоточность в Java: потоки, синхронизация, wait/notify и concurrent блокировки

Это полное руководство по многопоточности в Java с примерами потоков, синхронизации, wait/notify, concurrent блокировок и семафоров.

Коротко

Многопоточность позволяет выполнять задачи параллельно. Синхронизация гарантирует согласованный доступ к данным, а concurrent блокировки предоставляют современные механизмы управления потоками.

Определение

Многопоточность – это способность программы одновременно выполнять несколько потоков. Каждый поток имеет собственный stack, но они всем делят heap памяти.

Основные концепции:

Жизненный цикл потока

  • NEW: поток создан, но не запущен
  • RUNNABLE: готов к выполнению (выполняется или ждёт планировщика)
  • BLOCKED: ожидает блокировки монитора
  • WAITING: ожидает сигнала условия без временного ограничения
  • TIMED_WAITING: ожидает с временным ограничением
  • TERMINATED: поток завершён

Механизмы синхронизации

  • synchronized: синхронизация на основе монитора
  • wait()/notify(): коммуникация между потоками
  • ReentrantLock: гибкий механизм блокировки
  • ReadWriteLock: отдельные блокировки для чтения и записи
  • Semaphore: управление доступом через счётчик
  • CountDownLatch: ожидание завершения нескольких потоков
  • CyclicBarrier: точка синхронизации потоков

Потокобезопасность

  • Immutable Objects: безопасны по своей природе
  • Thread-Local: данные, специфичные для потока
  • Volatile: видимость значений переменных между потоками
  • Atomic Classes: операции без блокировок

Ключевые моменты

  • Потоки: лёгкие процессы со своим stack
  • Синхронизация: защита общих ресурсов
  • Монитор: синхронизационный механизм на уровне объекта
  • wait/notify: обмен сигналами между потоками
  • Deadlock: взаимная блокировка
  • Race Condition: неконтролируемый доступ к общим данным
  • Volatile: гарантирует видимость изменений между потоками
  • Производительность: важна для параллельных приложений

Основные компоненты

  1. Управление потоками: создание, управление, жизненный цикл
  2. Синхронизация: synchronized, блокировки, семафоры
  3. Коммуникация между потоками: wait/notify, blocking queues
  4. Потокобезопасные коллекции: concurrent структуры данных
  5. Пулы потоков: ExecutorService, ThreadPoolExecutor
  6. Потокобезопасность: immutable объекты, volatile, atomic классы
  7. Производительность: гранулярность блокировок, снижение contention
  8. Отладка: thread dump, race conditions, deadlocks

Примеры кода

1. Базовые операции с потоками

public class ThreadGrundlagen {
    
    // Поток через наследование от 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 + " - Зählung: " + i);
                try {
                    Thread.sleep(500); // 500ms пауза
                } catch (InterruptedException e) {
                    System.out.println(name + " был прерван");
                    return;
                }
            }
            System.out.println(name + " завершён");
        }
    }
    
    // Поток через реализацию Runnable
    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 + " - Работа: " + i);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    System.out.println(name + " прерван");
                    return;
                }
            }
            System.out.println(name + " готов");
        }
    }
    
    // Lambda-выражение как Runnable
    static void lambdaThreadDemo() {
        Thread lambdaThread = new Thread(() -> {
            for (int i = 1; i <= 3; i++) {
                System.out.println("Lambda поток - Шаг " + i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return;
                }
            }
        });
        
        lambdaThread.start();
    }
    
    public static void main(String[] args) {
        System.out.println("=== Базовые операции с потоками ===");
        
        // Поток через наследование
        MeinThread thread1 = new MeinThread("Thread-1");
        thread1.start();
        
        // Поток через Runnable
        Thread thread2 = new Thread(new MeinRunnable("Runnable-1"));
        thread2.start();
        
        // Lambda поток
        lambdaThreadDemo();
        
        // Ожидание завершения потоков
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Главный поток завершён");
    }
}

2. Синхронизация с synchronized

public class SynchronisationDemo {
    
    // Общий ресурс
    static class Zaehler {
        private int wert = 0;
        
        // Синхронизированный метод
        public synchronized void erhoehen() {
            wert++;
            System.out.println(Thread.currentThread().getName() + 
                             " увеличивает на: " + wert);
        }
        
        // Синхронизированный блок
        public void verringern() {
            synchronized(this) {
                wert--;
                System.out.println(Thread.currentThread().getName() + 
                                 " уменьшает на: " + wert);
            }
        }
        
        public synchronized int getWert() {
            return wert;
        }
    }
    
    // Producer-Consumer с wait/notify
    static class Warenlager {
        private final int[] lager = new int[5];
        private int index = 0;
        
        public synchronized void einlagern(int ware) throws InterruptedException {
            // Ждём, если хранилище полно
            while (index >= lager.length) {
                System.out.println("Хранилище полно - производитель ждёт");
                wait();
            }
            
            lager[index] = ware;
            index++;
            System.out.println(Thread.currentThread().getName() + 
                             " добавил: " + ware);
            
            // Уведомляем потребителя
            notifyAll();
        }
        
        public synchronized int auslagern() throws InterruptedException {
            // Ждём, если хранилище пусто
            while (index <= 0) {
                System.out.println("Хранилище пусто - потребитель ждёт");
                wait();
            }
            
            index--;
            int ware = lager[index];
            System.out.println(Thread.currentThread().getName() + 
                             " извлёк: " + ware);
            
            // Уведомляем производителя
            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("=== Демонстрация синхронизации ===");
        
        // Простая синхронизация
        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);
        }
        
        // Запускаем потоки
        for (Thread t : threads) {
            t.start();
        }
        
        // Ожидаем завершения потоков
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Финальное значение: " + zaehler.getWert());
        
        // Демонстрация Producer-Consumer
        System.out.println("\n=== Демонстрация Producer-Consumer ===");
        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 и современная синхронизация

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();
        }
    }
}

ReentrantLock предоставляет явное управление блокировками с полной переносимостью в отличие от встроенного synchronized. Посмотрите на пример банковского счета: мы защищаем изменения баланса блокировкой, гарантируя, что операции не перекрываются. Вызовы lock() и unlock() в блоке try-finally обеспечивают безопасное освобождение даже при исключениях.

ReadWriteLock решает задачу по-другому. Когда много потоков читают одни и те же данные, а пишет только один, блокировка на чтение позволяет им работать параллельно. Писатель же получает исключительный доступ. В примере с ThreadSafeList три читателя могут одновременно получать снимок списка, пока писатель медленно добавляет элементы.

Semaphore контролирует доступ к ресурсам, ограничивая число потоков, работающих с ними одновременно. Это похоже на очередь у парковки с фиксированным числом мест. Каждый acquire() ждет, пока освободится место, а release() сообщает остальным, что ресурс больше не нужен.

CountDownLatch используется для синхронизации нескольких потоков в одной точке. Главный поток создает лatch с числом, равным количеству рабочих, и ждет на await(). Каждый рабочий по завершении вызывает countDown(). Когда счетчик достигает нуля, основной поток продолжает работу. Это удобно для стартовых сигналов и сбора результатов.

4. Потокобезопасные структуры данных и Atomic классы

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

public class ThreadSafeCollections {
    
    // Atomic Classes Demo
    static class AtomicZaehler {
        private final AtomicInteger zaehler = new AtomicInteger(0);
        private final AtomicLong longZaehler = new AtomicLong(0);
        private final AtomicBoolean flag = new AtomicBoolean(false);
        private final AtomicReference<String> nachricht = new AtomicReference<>("");
        
        public void increment() {
            int alterWert = zaehler.getAndIncrement();
            System.out.println(Thread.currentThread().getName() + 
                             " increment: " + alterWert + " -> " + zaehler.get());
        }
        
        public void add(long wert) {
            long alterWert = longZaehler.getAndAdd(wert);
            System.out.println(Thread.currentThread().getName() + 
                             " add: " + alterWert + " + " + wert + " -> " + longZaehler.get());
        }
        
        public void toggleFlag() {
            boolean alterWert = flag.getAndSet(!flag.get());
            System.out.println(Thread.currentThread().getName() + 
                             " toggle: " + alterWert + " -> " + flag.get());
        }
        
        public void updateNachricht(String neueNachricht) {
            String alteNachricht = nachricht.getAndSet(neueNachricht);
            System.out.println(Thread.currentThread().getName() + 
                             " update: '" + alteNachricht + "' -> '" + neueNachricht + "'");
        }
        
        public int getZaehler() { return zaehler.get(); }
        public long getLongZaehler() { return longZaehler.get(); }
        public boolean getFlag() { return flag.get(); }
        public String getNachricht() { return nachricht.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");
            
            // Запускаем все потоки
            reader.start();
            for (Thread writer : writers) {
                writer.start();
            }
            
            // Ждем завершения потоков
            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 wert = threadLocalValue.get();
                        String name = threadLocalName.get();
                        
                        System.out.println(name + " wert: " + wert);
                        
                        // Изменяем значение ThreadLocal
                        threadLocalValue.set(wert + threadId);
                        
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                    
                    // Очищаем 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 ===");
        AtomicZaehler zaehler = new AtomicZaehler();
        
        Thread[] atomicThreads = new Thread[4];
        for (int i = 0; i < atomicThreads.length; i++) {
            final int threadId = i;
            atomicThreads[i] = new Thread(() -> {
                zaehler.increment();
                zaehler.add(threadId * 10);
                zaehler.toggleFlag();
                zaehler.updateNachricht("Nachricht von 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("Zaehler: " + zaehler.getZaehler());
        System.out.println("LongZaehler: " + zaehler.getLongZaehler());
        System.out.println("Flag: " + zaehler.getFlag());
        System.out.println("Nachricht: " + zaehler.getNachricht());
        
        // Concurrent Collections Demo
        ConcurrentCollectionsDemo.hashMapDemo();
        ConcurrentCollectionsDemo.blockingQueueDemo();
        
        // ThreadLocal Demo
        ThreadLocalDemo.demo();
    }
}

Thread-Pools и 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);

// Tasks ausführen
Future<String> future = fixedPool.submit(() -> {
    Thread.sleep(1000);
    return "Ergebnis";
});

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

// Pool herunterfahren
fixedPool.shutdown();
scheduledPool.shutdown();

Избежание Deadlock

Условия возникновения Deadlock

  1. Mutual Exclusion: ресурс может использоваться только одним потоком
  2. Hold and Wait: поток удерживает ресурсы и ждёт другие
  3. No Preemption: ресурсы невозможно отобрать принудительно
  4. Circular Wait: циклическая цепочка ожидания между потоками

Стратегии предотвращения

// Lock Ordering - всегда захватывать блокировки в одном порядке
public void transfer(Account from, Account to, double amount) {
    // Синхронизируем аккаунты в детерминированном порядке
    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 с 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;
        }
        // Небольшая пауза перед следующей попыткой
        Thread.sleep(100);
    }
}

Оптимизация производительности

Гранулярность блокировок

// Грубая синхронизация (плохо)
public synchronized void addElement(Object element) {
    // Вся операция заблокирована
    list.add(element);
    size++;
}

// Тонкая синхронизация (лучше)
public void addElement(Object element) {
    synchronized(list) {
        list.add(element);
    }
    synchronized(this) {
        size++;
    }
}

Volatile vs Synchronized

// Volatile для простой видимости переменной
private volatile boolean running = true;

public void stop() {
    running = false; // Видно всем потокам
}

public void run() {
    while (running) {
        // Выполнить работу
    }
}

// Synchronized для сложных операций
private int counter = 0;

public synchronized void increment() {
    counter++; // Атомарная операция
}

Преимущества и недостатки

Преимущества многопоточности

  • Производительность: параллельное выполнение на многоядерных системах
  • Отзывчивость: интерфейс остаётся реактивным во время длительных операций
  • Использование ресурсов: лучшая загрузка системных ресурсов
  • Масштабируемость: задачи можно распределять между потоками

Недостатки

  • Сложность: синхронизация подвержена ошибкам
  • Отладка: race conditions сложно воспроизвести
  • Накладные расходы: создание потоков и переключение контекста требуют времени
  • Ресурсы: повышенное потребление памяти и CPU

Типичные вопросы на собеседовании

  1. Какая разница между wait() и sleep()? wait() освобождает блокировку, sleep() удерживает её. wait() требует synchronized, sleep() нет.

  2. Объясните Deadlock и способы его предотвращения! Deadlock это взаимная блокировка потоков. Предотвращение через Lock Ordering и TryLock с timeout.

  3. Когда использовать volatile вместо synchronized? volatile для простой видимости переменных, synchronized для сложных операций.

  4. Чем Runnable отличается от Callable? Runnable не возвращает результат, Callable возвращает результат и может генерировать исключения.

Основные источники

  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
Назад к блогу
Share:

Похожие статьи