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

Какие знаешь варианты синхронизации?

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

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

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

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

Какие знаешь варианты синхронизации?

В Java существует несколько подходов для синхронизации доступа к общим ресурсам в многопоточной среде. Рассмотрю основные варианты.

1. Synchronized методы

Самый простой способ синхронизации. Поток должен захватить монитор объекта перед выполнением метода.

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

2. Synchronized блоки

Позволяет синхронизировать только часть кода, повышая производительность.

public class BankAccount {
    private double balance = 1000;
    private Object lock = new Object();
    
    public void withdraw(double amount) {
        // Несинхронизированный код
        validateAmount(amount);
        
        // Синхронизированная часть
        synchronized(lock) {
            if(balance >= amount) {
                balance -= amount;
            }
        }
    }
    
    // Синхронизация на другом объекте
    private Object transferLock = new Object();
    public void transfer(BankAccount other, double amount) {
        synchronized(transferLock) {
            this.withdraw(amount);
            other.deposit(amount);
        }
    }
}

3. Volatile переменные

Гарантирует видимость изменений между потоками, но не атомарность операций.

public class VolatileExample {
    private volatile boolean flag = false;  // всегда читаем/пишем из памяти
    private volatile int counter = 0;
    
    public void setFlag(boolean value) {
        flag = value;  // немедленно видно всем потокам
    }
    
    public boolean getFlag() {
        return flag;   // читаем актуальное значение
    }
    
    // Внимание: counter++ не атомарна, даже с volatile!
    // Нужно использовать AtomicInteger или synchronized
}

4. Atomic классы (java.util.concurrent.atomic)

Обеспечивают атомарные операции без использования блокировок (lock-free).

public class AtomicExample {
    private AtomicInteger counter = new AtomicInteger(0);
    private AtomicBoolean running = new AtomicBoolean(true);
    private AtomicReference<String> name = new AtomicReference<>("initial");
    
    public void increment() {
        counter.incrementAndGet();          // ++counter
        counter.addAndGet(5);               // counter += 5
        counter.getAndSet(10);              // oldValue = counter; counter = 10
        counter.compareAndSet(10, 20);      // if(counter == 10) counter = 20
    }
    
    public boolean isRunning() {
        return running.get();
    }
    
    public void stop() {
        running.set(false);
    }
}

5. ReentrantLock (явная блокировка)

Больше возможностей, чем synchronized: попытка захвата, попытка с таймаутом, справедливость.

public class LockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int counter = 0;
    
    // Базовое использование
    public void increment() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();  // важно в finally!
        }
    }
    
    // Попытка захватить с таймаутом
    public boolean tryIncrement() {
        try {
            if(lock.tryLock(100, TimeUnit.MILLISECONDS)) {
                try {
                    counter++;
                    return true;
                } finally {
                    lock.unlock();
                }
            }
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }
    
    // Справедливая блокировка (FIFO)
    private final ReentrantLock fairLock = new ReentrantLock(true);
}

6. ReadWriteLock

Позволяет множество читателей одновременно, но только одного писателя.

public class CacheWithReadWriteLock {
    private final Map<String, String> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public String get(String key) {
        lock.readLock().lock();  // Множество потоков могут читать
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(String key, String value) {
        lock.writeLock().lock();  // Только один поток пишет
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

7. Semaphore

Ограничивает количество потоков, имеющих доступ к ресурсу.

public class SemaphoreExample {
    private final Semaphore semaphore = new Semaphore(3);  // 3 потока одновременно
    
    public void accessResource() {
        try {
            semaphore.acquire();  // Уменьшить счётчик
            // Критическая секция (максимум 3 потока)
            doWork();
        } finally {
            semaphore.release();  // Увеличить счётчик
        }
    }
}

8. CountDownLatch

Позволяет потокам ждать, пока другие потоки выполнят определённые действия.

public class CountDownLatchExample {
    public void waitForMultipleThreads() {
        CountDownLatch latch = new CountDownLatch(3);
        
        for(int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println("Work started");
                // Выполнить работу
                System.out.println("Work finished");
                latch.countDown();  // Уменьшить счётчик
            }).start();
        }
        
        // Главный поток ждёт
        try {
            latch.await();  // Ждёт, пока счётчик обнулится
            System.out.println("All threads finished");
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

9. CyclicBarrier

Позволяет группе потоков дождаться друг друга в определённой точке.

public class CyclicBarrierExample {
    public void waitForAllThreads() {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> 
            System.out.println("All threads reached barrier"));
        
        for(int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " waiting");
                    barrier.await();  // Ждёт других потоков
                    System.out.println(Thread.currentThread().getName() + " continuing");
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

10. Synchronized Collections vs Concurrent Collections

// Synchronized (старый подход)
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

// Concurrent (современный подход - лучше!)
Map<String, String> concurrentMap = new ConcurrentHashMap<>();

// ConcurrentHashMap использует сегментную блокировку (Bucket locking)
// вместо блокировки всего объекта, что даёт лучшую производительность
List<String> list = new CopyOnWriteArrayList<>();  // Для частых чтений

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

ПодходПроизводительностьСложностьГибкость
SynchronizedНизкая (грубая блокировка)ПростаяНизкая
VolatileВысокая (но не для ++i)ПростаяНизкая
AtomicXXВысокая (lock-free)СредняяНизкая
ReentrantLockСредняяСложнаяВысокая
ReadWriteLockВысокая (много читателей)СложнаяВысокая
Concurrent CollectionsВысокаяПростаяСредняя

Лучшие практики

  1. Используй Concurrent Collections по умолчанию
  2. Для простых счётчиков - AtomicInteger
  3. Для критических секций малого размера - synchronized
  4. Для сложной синхронизации - ReentrantLock
  5. Избегай synchronized методы на public объектах (делай блоки)
  6. Всегда используй try-finally для освобождения ресурсов
  7. Предпочитай неизменяемость (immutable objects) синхронизации