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

Как синхронизировать поток

1.7 Middle🔥 191 комментариев
#Многопоточность

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

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

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

Как синхронизировать поток

Синхронизация потоков — одна из самых важных тем в Java. Неправильная синхронизация приводит к race conditions, deadlocks и потере данных.

Проблема: Race Condition

Сначала покажу проблему, которую решает синхронизация:

public class Counter {
    private int count = 0;
    
    public void increment() {
        count++;  // ПРОБЛЕМА: это 3 операции в JVM
    }
    
    public int getCount() {
        return count;
    }
}

Если два потока одновременно вызовут increment(), инкремент произойдёт только один раз вместо двух — это race condition.

Вариант 1: synchronized (самый простой)

Использую synchronized для защиты критичных секций:

public class SynchronizedCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public void incrementAndLog() {
        synchronized (this) {
            count++;
        }
        log.info("Count: {}", count);
    }
    
    public synchronized int getCount() {
        return count;
    }
}

Как это работает: каждый объект имеет встроенный монитор (lock). Только один поток может выполнять синхронизированный код.

Плюсы: простота, встроенный механизм Минусы: монолитные блокировки, нет fairness

Вариант 2: ReentrantLock (более гибкий)

Для сложных сценариев использую ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    
    public boolean incrementWithTimeout() {
        try {
            if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
                try {
                    count++;
                    return true;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }
}

Плюсы: tryLock() с timeout, fair locks, возможность проверить состояние Минусы: нужно явно вызывать unlock(), больше boilerplate

Вариант 3: ReadWriteLock (для читаемых данных)

Если много читателей и мало писателей:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedData {
    private String cachedValue;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public String read() {
        lock.readLock().lock();
        try {
            return cachedValue;  // Много потоков могут читать одновременно
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void write(String value) {
        lock.writeLock().lock();
        try {
            this.cachedValue = value;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Плюсы: параллельное чтение, быстро при read-heavy Минусы: медленнее при write-heavy

Вариант 4: AtomicInteger (для примитивов)

Для простых значений используй Atomic классы:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
    
    public boolean compareAndSet(int expected, int newValue) {
        return count.compareAndSet(expected, newValue);
    }
}

Плюсы: очень быстро, lock-free, минимальный overhead Минусы: работает только с примитивами

Вариант 5: volatile (для флагов)

Когда нужно видеть изменения из других потоков:

public class VolatileFlag {
    private volatile boolean shutdown = false;
    
    public void setShutdown() {
        shutdown = true;
    }
    
    public boolean isShutdown() {
        return shutdown;
    }
}

Плюсы: минимальный overhead Минусы: не защищает от race conditions при сложной логике

Вариант 6: ConcurrentHashMap

Для потокобезопасных коллекций:

import java.util.concurrent.ConcurrentHashMap;

public class UserCache {
    private final ConcurrentHashMap<String, User> cache = new ConcurrentHashMap<>();
    
    public void put(String key, User user) {
        cache.put(key, user);
    }
    
    public User get(String key) {
        return cache.get(key);
    }
}

Сравнительная таблица

МеханизмСложностьПроизводительность
synchronizedНизкаяСредняя
ReentrantLockСредняяВысокая
ReadWriteLockСредняяВысокая (read-heavy)
AtomicIntegerНизкаяОчень высокая
volatileОчень низкаяОчень высокая
ConcurrentHashMapНизкаяВысокая

Мой выбор для разных сценариев

  1. Простой счётчикAtomicInteger
  2. Критичная секция кодаsynchronized или ReentrantLock
  3. Кэш с читателямиConcurrentHashMap + ReadWriteLock

Главное правило: Используй наименее сложный механизм, который решает твою задачу. Оверсинхронизация приводит к deadlocks и проблемам производительности.