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

Для чего нужен synchronized блок?

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

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

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

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

Synchronized блок в Java: механизм синхронизации

Synchronized блок — это фундаментальный механизм для обеспечения потокобезопасности в многопоточных приложениях. Он гарантирует, что только один поток может выполнять код внутри блока одновременно.

Основное назначение

Synchronized блок нужен для:

  • Защиты от race conditions — когда несколько потоков одновременно обращаются к общему ресурсу
  • Обеспечения atomicity — операции выполняются неделимо
  • Гарантирования visibility — изменения, сделанные одним потоком, видны другим
  • Сохранения консистентности данных — предотвращение корупции состояния

Как работает synchronized

Synchronized использует монитор (intrinsic lock) каждого объекта. Когда поток входит в synchronized блок:

  1. Проверяет, свободен ли монитор объекта
  2. Если да — захватывает его и входит в блок
  3. Другие потоки ждут в очереди
  4. После выхода — освобождает монитор
// Синхронизация по конкретному объекту
private final Object lock = new Object();
private int counter = 0;

public void incrementCounter() {
    synchronized (lock) {  // только один поток одновременно
        counter++;  // критическая секция
    }
}

Варианты использования

1. Синхронизированные методы

public class Counter {
    private int count = 0;
    
    // Монитор — сам объект (this)
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

2. Синхронизированные блоки (тоньше контроль)

public class BankAccount {
    private double balance = 0;
    private List<String> transactions = new ArrayList<>();
    
    public void transfer(double amount) {
        // Синхронизируем только самое важное
        synchronized (this) {
            balance -= amount;
        }
        // Дорогая операция вне synchronized
        logTransaction("transfer", amount);
    }
}

3. Синхронизация по разным объектам (для параллелизма)

public class Wallet {
    private final Object incomeLock = new Object();
    private final Object expenseLock = new Object();
    private double income = 0;
    private double expenses = 0;
    
    public void addIncome(double amount) {
        synchronized (incomeLock) {  // не блокирует expenses
            income += amount;
        }
    }
    
    public void addExpense(double amount) {
        synchronized (expenseLock) {  // не блокирует income
            expenses += amount;
        }
    }
}

4. Синхронизация статических методов

public class GlobalCounter {
    private static int count = 0;
    private static final Object lock = new Object();
    
    // Монитор — сам класс (GlobalCounter.class)
    public static synchronized void increment() {
        count++;
    }
    
    // Эквивалентно:
    public static void incrementManual() {
        synchronized (GlobalCounter.class) {
            count++;
        }
    }
}

Типичные ошибки

1. Забывчивость про synchronized при доступе

// ❌ ОПАСНО: забыли synchronized при чтении
public int unsafeRead() {
    return count;  // может прочитать неполное значение
}

// ✅ ПРАВИЛЬНО: оба метода synchronized
public synchronized int getCount() {
    return count;
}

2. Синхронизация по неправильному объекту

// ❌ ОПАСНО: каждый раз новый объект
Object lock = new Object();  // создаётся в методе!
synchronized (lock) { ... }  // каждый раз разные потоки захватывают разные мониторы

// ✅ ПРАВИЛЬНО: final field
private final Object lock = new Object();

3. Deadlock — взаимная блокировка

// ❌ ОПАСНО: может привести к deadlock
Object lock1 = new Object();
Object lock2 = new Object();

// Поток A: захватывает lock1, ждёт lock2
synchronized (lock1) {
    synchronized (lock2) { ... }
}

// Поток B: захватывает lock2, ждёт lock1
synchronized (lock2) {
    synchronized (lock1) { ... }
}  // DEADLOCK! Оба потока ждут друг друга

Альтернативы synchronized

Для современного Java (5+) существуют более гибкие инструменты:

// ReentrantLock — явное управление
private final Lock lock = new ReentrantLock();
public void someMethod() {
    lock.lock();
    try {
        // критическая секция
    } finally {
        lock.unlock();
    }
}

// AtomicInteger — для простых счётчиков
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
    counter.incrementAndGet();
}

// ConcurrentHashMap — потокобезопасная коллекция
Map<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");  // потокобезопасна внутри

Производительность

Synchronized — это дорогая операция:

  • Захват/освобождение монитора требует time
  • Другие потоки блокируются
  • JVM не может оптимизировать код внутри synchronized

Best practices:

  • Держи synchronized блоки как можно короче
  • Синхронизируй только критические секции
  • Рассмотри современные альтернативы (Lock, Atomic, Concurrent*)
  • Для высоконагруженных систем используй lock-free структуры

Заключение

Synchronized блок — это базовый, но мощный инструмент для защиты потокобезопасности. Понимание механики монитора, правильное применение и знание альтернатив — критичные навыки для Java developer, работающего с многопоточностью.

Для чего нужен synchronized блок? | PrepBro