← Назад к вопросам

Какие знаешь ключевые слова для многопоточности в Java?

1.7 Middle🔥 111 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ключевые слова для многопоточности в Java

Многопоточность — это способность программы выполнять несколько задач одновременно. Java имеет встроенную поддержку многопоточности через несколько ключевых слов и механизмов синхронизации.

1. synchronized

Используется для синхронизации доступа к общим ресурсам. Только один поток может выполнять synchronized блок одновременно.

// Синхронизация метода (весь метод как critical section)
public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++; // Только один поток за раз
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// Синхронизация блока (более гибко)
public class BankAccount {
    private double balance = 0;
    private Object lock = new Object();
    
    public void deposit(double amount) {
        synchronized(lock) { // Блокирует только необходимую часть
            balance += amount;
            System.out.println("Deposited: " + amount);
        }
    }
    
    public void withdraw(double amount) {
        synchronized(lock) {
            if (balance >= amount) {
                balance -= amount;
                System.out.println("Withdrew: " + amount);
            }
        }
    }
}

// Синхронизация на объекте класса
public class SingletonWithSync {
    private static SingletonWithSync instance;
    
    public static SingletonWithSync getInstance() {
        synchronized(SingletonWithSync.class) {
            if (instance == null) {
                instance = new SingletonWithSync();
            }
        }
        return instance;
    }
}

Проблемы: может привести к deadlock'ам, performance overhead, неявная блокировка.

2. volatile

Гарантирует, что значение переменной видно всем потокам, не кэшируется в CPU кэше.

public class VolatileExample {
    // БЕЗ volatile: поток может кэшировать значение
    private boolean shouldStop = false;
    
    // С volatile: всегда читает актуальное значение
    private volatile boolean running = true;
    
    public void startWorker() {
        new Thread(() -> {
            while (running) { // Видит обновления из других потоков
                doWork();
            }
            System.out.println("Worker stopped");
        }).start();
    }
    
    public void stopWorker() {
        running = false; // Все потоки сразу увидят true
    }
}

// Volatile для флагов
public class Application {
    private volatile boolean applicationStarted = false;
    
    public void start() {
        applicationStarted = true;
    }
    
    public boolean isRunning() {
        return applicationStarted; // Всегда актуальное значение
    }
}

Используй для: флаги, счетчики, простые значения. НЕ используй: замену synchronized, для сложной синхронизации.

3. wait() и notify()

Механизм внутри synchronized для коммуникации между потоками через monitor.

public class ProducerConsumer {
    private Queue<Integer> buffer = new LinkedList<>();
    private int maxSize = 10;
    
    public synchronized void produce(int value) throws InterruptedException {
        while (buffer.size() == maxSize) {
            wait(); // Ждет, пока буфер освободится
        }
        buffer.add(value);
        System.out.println("Produced: " + value);
        notifyAll(); // Пробуждает все ждущие потоки
    }
    
    public synchronized int consume() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // Ждет, пока буфер заполнится
        }
        int value = buffer.poll();
        System.out.println("Consumed: " + value);
        notifyAll(); // Пробуждает производителей
        return value;
    }
}

// wait() vs notify()
wait();        // Отпускает монитор и ждет
notify();      // Пробуждает ОДИН ждущий поток (неопределенный)
notifyAll();   // Пробуждает ВСЕ ждущие потоки (безопаснее)

4. Ключевые слова в Thread классе

Thread.sleep(long millis) — паузирует текущий поток:

public class TimedTask {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Start");
        Thread.sleep(2000); // Ждет 2 секунды
        System.out.println("End"); // Выполнится через 2 сек
    }
}

Thread.join() — ждет завершения другого потока:

Thread workerThread = new Thread(() -> {
    System.out.println("Worker started");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Worker finished");
});

workerThread.start();
workerThread.join(); // Главный поток ждет завершения worker'а
System.out.println("Main continues");

// Output:
// Worker started
// (2 seconds pause)
// Worker finished
// Main continues

Thread.yield() — подсказывает scheduler'у дать возможность другим потокам:

for (int i = 0; i < 1000000; i++) {
    doWork();
    if (i % 1000 == 0) {
        Thread.yield(); // Может позволить другим потокам выполниться
    }
}

5. Java Concurrent API (modern approach)

Модерная альтернатива synchronized — более гибкая и мощная.

ReentrantLock — явная блокировка (лучше контроль):

import java.util.concurrent.locks.ReentrantLock;

public class ModernCounter {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // ВАЖНО: unlock в finally
        }
    }
    
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

// С try-with-resources (если используется StampedLock)
public class StampedLockExample {
    private StampedLock lock = new StampedLock();
    private int value = 0;
    
    public void update(int newValue) {
        long stamp = lock.writeLock(); // Захватить write lock
        try {
            value = newValue;
        } finally {
            lock.unlockWrite(stamp); // Освободить
        }
    }
    
    public int read() {
        long stamp = lock.readLock(); // Read lock (может быть несколько)
        try {
            return value;
        } finally {
            lock.unlockRead(stamp);
        }
    }
}

Condition — для координации (замена wait/notify):

public class BoundedBuffer<T> {
    private Queue<T> buffer = new LinkedList<>();
    private int maxSize;
    private ReentrantLock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();
    
    public BoundedBuffer(int maxSize) {
        this.maxSize = maxSize;
    }
    
    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (buffer.size() == maxSize) {
                notFull.await(); // Как wait()
            }
            buffer.add(item);
            notEmpty.signalAll(); // Как notifyAll()
        } finally {
            lock.unlock();
        }
    }
    
    public T get() throws InterruptedException {
        lock.lock();
        try {
            while (buffer.isEmpty()) {
                notEmpty.await();
            }
            T item = buffer.poll();
            notFull.signalAll();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

AtomicInteger, AtomicReference — потокобезопасные переменные без блокировок (lock-free):

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet(); // Атомарная операция
    }
    
    public int getCount() {
        return count.get();
    }
}

public class AtomicLinkedList {
    private AtomicReference<Node> head = new AtomicReference<>();
    
    public void add(int value) {
        Node newNode = new Node(value);
        while (true) {
            Node oldHead = head.get();
            newNode.next = oldHead;
            if (head.compareAndSet(oldHead, newNode)) {
                return; // Успешно добавили
            }
            // Retry если другой поток изменил head
        }
    }
}

Сравнение подходов

ПодходПлюсыМинусыИспользуй для
synchronizedПростой синтаксисDeadlock риск, performanceПростая синхронизация
volatileЛегкий, без overheadТолько для простых случаевФлаги, числовые значения
ReentrantLockКонтроль, tryLock()Сложнее, нужен finallyСложная синхронизация
Atomic*Lock-free, быстроСложная логикаСчетчики, ссылки
concurrent collectionsГотовые thread-safeНе все случаи покрываютConcurrentHashMap, CopyOnWriteArrayList

Best Practices для многопоточности

  1. Избегай synchronized если можешь:
// Плохо: synchronized везде
public synchronized void method1() {
    // много кода
}

// Хорошо: только критичная секция
public void method1() {
    // некритичный код
    synchronized(lock) {
        // только критичная часть
    }
}
  1. Используй concurrent коллекции:
// Плохо
Map<String, Integer> map = new HashMap<>();
synchronized(map) { // Каждый раз блокировка
    map.put("key", 1);
}

// Хорошо
Map<String, Integer> map = new ConcurrentHashMap<>(); // Уже потокобезопасна
map.put("key", 1);
  1. Используй ExecutorService для потоков:
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    final int taskId = i;
    executor.submit(() -> doTask(taskId));
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);

Многопоточность — сложная тема, требующая внимания к деталям. Ошибки в многопоточных системах очень трудно воспроизвести и отладить.