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

Что такое монитор для синхронизации?

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

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

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

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

Монитор для синхронизации в Java

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

Основной концепт

В многопоточном приложении несколько потоков могут одновременно работать с одним объектом, что может привести к race conditions (гонкам) и несогласованности данных. Монитор гарантирует, что только один поток может выполнять synchronized код объекта одновременно.

Как работает монитор

public class BankAccount {
    private int balance = 1000;
    
    // Без синхронизации — race condition!
    public void withdraw(int amount) {
        if (balance >= amount) {
            balance = balance - amount;  // Потокоопасность?
        }
    }
    
    // С синхронизацией — безопасно
    public synchronized void withdrawSafe(int amount) {
        if (balance >= amount) {
            balance = balance - amount;  // Теперь безопасно
        }
    }
}

Когда поток вызывает synchronized метод:

  1. Поток пытается захватить монитор объекта (lock)
  2. Если монитор свободен — поток захватывает его и входит в метод
  3. Другие потоки ждут в очереди (wait queue)
  4. После выхода из метода — монитор освобождается
  5. Следующий поток из очереди может захватить монитор

Synchronized методы

public class Counter {
    private int count = 0;
    
    // Синхронизированный метод — монитор захватывается на весь метод
    public synchronized void increment() {
        count++;  // Thread-safe
    }
    
    public synchronized int getCount() {
        return count;  // Thread-safe read
    }
    
    // Эквивалентно:
    public void incrementManual() {
        synchronized(this) {
            count++;
        }
    }
}

Synchronized блоки

Для более гранулярной синхронизации можно использовать synchronized блоки:

public class DataProcessor {
    private List<String> data = new ArrayList<>();
    private int counter = 0;
    
    public void processData(String item) {
        // Дорогостоящее вычисление — без синхронизации
        String processed = expensiveOperation(item);
        
        // Только критическая секция синхронизирована
        synchronized(this) {
            data.add(processed);
            counter++;
        }
    }
    
    // Можно синхронизировать по разным объектам
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    
    private List<String> list1;
    private List<String> list2;
    
    public void addToList1(String item) {
        synchronized(lock1) {
            list1.add(item);
        }
    }
    
    public void addToList2(String item) {
        synchronized(lock2) {
            list2.add(item);
        }
    }
    // Эти методы не блокируют друг друга!
}

Synchronized на статических методах

public class Logger {
    private static int messageCount = 0;
    
    // Синхронизация по классу, не по объекту
    public static synchronized void log(String message) {
        messageCount++;
        System.out.println(messageCount + ": " + message);
    }
    
    // Эквивалентно:
    public static void logManual(String message) {
        synchronized(Logger.class) {
            messageCount++;
            System.out.println(messageCount + ": " + message);
        }
    }
}

Wait, Notify и Notify All

Мониторы поддерживают методы для координации потоков:

public class ProducerConsumer {
    private Queue<String> queue = new LinkedList<>();
    private final int CAPACITY = 10;
    
    public synchronized void produce(String item) throws InterruptedException {
        // Ждём, пока очередь не освободится
        while (queue.size() >= CAPACITY) {
            wait();  // Освобождаю монитор и жду сигнала
        }
        
        queue.add(item);
        notifyAll();  // Пробуждаю потребителей
    }
    
    public synchronized String consume() throws InterruptedException {
        // Ждём, пока в очереди что-то появится
        while (queue.isEmpty()) {
            wait();  // Ожидание...
        }
        
        String item = queue.poll();
        notifyAll();  // Пробуждаю производителей
        return item;
    }
}

Проблемы с монитором

1. Deadlock (взаимная блокировка)

// ОПАСНО — может привести к deadlock
public class Account {
    private int balance;
    
    public synchronized void transferTo(Account other, int amount) {
        if (this.balance >= amount) {
            this.balance -= amount;
            other.deposit(amount);  // Попытка захватить другой монитор!
        }
    }
    
    public synchronized void deposit(int amount) {
        this.balance += amount;
    }
}

// Если потоки делают одновременно:
// Thread1: account1.transferTo(account2, 100);  // Захватил account1
// Thread2: account2.transferTo(account1, 50);   // Захватил account2
// => DEADLOCK!

Решение — установить порядок захвата:

public void transferTo(Account other, int amount) {
    Account first = this.id < other.id ? this : other;
    Account second = this.id < other.id ? other : this;
    
    synchronized(first) {
        synchronized(second) {
            // Теперь безопасно
        }
    }
}

2. Performance Issues

// Плохо — синхронизирует весь метод
public synchronized List<String> getAllUsers() {
    List<String> result = new ArrayList<>();
    for (User user : users) {  // Дорогостоящий цикл
        result.add(user.getName());
    }
    return result;
}

// Хорошо — синхронизирует минимум
public List<String> getAllUsersBetter() {
    List<String> copy;
    synchronized(this) {
        copy = new ArrayList<>(users);  // Копируем быстро
    }
    return copy.stream()
        .map(User::getName)
        .collect(Collectors.toList());  // Обработка без блокировки
}

Современные альтернативы

// Lock interface (более гибко)
Lock lock = new ReentrantLock();
lock.lock();
try {
    // Критическая секция
} finally {
    lock.unlock();
}

// ReadWriteLock (несколько читателей)
ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();   // Много потоков могут читать
rwLock.writeLock().lock();  // Только один может писать

// Atomic классы (для примитивов)
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();  // Thread-safe без синхронизации

// ConcurrentHashMap (синхронизированные коллекции)
Map<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");  // Thread-safe

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

✓ Используй synchronized для простых случаев ✓ Минимизируй время удержания монитора ✓ Избегай вложенной синхронизации (deadlock risk) ✓ Предпочитай Lock и Atomic для сложных сценариев ✓ Используй ConcurrentHashMap вместо Collections.synchronizedMap ✓ Тестируй многопоточный код на race conditions

Заключение

Монитор — это фундаментальный механизм синхронизации в Java. Каждый объект имеет встроенный монитор для управления доступом к его состоянию. Synchronized методы и блоки гарантируют, что только один поток может выполнять критическую секцию. Для более сложных сценариев используй Lock, ReadWriteLock и Atomic классы.

Что такое монитор для синхронизации? | PrepBro