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

Что значит захватить монитор?

1.7 Middle🔥 91 комментариев
#Docker, Kubernetes и DevOps

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

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

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

Что значит захватить монитор?

Захватить монитор (acquire monitor) в Java — это означает получить исключительное право на доступ к объекту в многопоточной программе. Это фундаментальный механизм синхронизации в Java, на котором построены почти все инструменты для управления конкурентным доступом к ресурсам.

Что такое монитор?

Монитор — это встроенный механизм синхронизации, связанный с каждым объектом Java. Каждый объект имеет:

  • Монитор (lock) — блокировка, которая может быть захвачена только одним потоком одновременно
  • Очередь ожидания — потоки, ждущие освобождения монитора
  • Условные переменные — методы wait() и notify() для синхронизации

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

С помощью ключевого слова synchronized:

public synchronized void criticalMethod() {
    // Этот метод доступен только одному потоку одновременно
    // Монитор объекта захватывается при входе в метод
    // И освобождается при выходе из метода
    System.out.println("Только один поток в этот момент");
}

С помощью блока synchronized:

public void someMethod() {
    synchronized(this) {
        // Захватываем монитор объекта this
        System.out.println("Критическая секция");
        // Монитор освобождается при выходе из блока
    }
    
    // Здесь монитор уже освобождён
}

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

public static synchronized void staticMethod() {
    // Захватывается монитор класса (Class объект)
    // Не монитор экземпляра, а монитор самого класса
}

Пример: проблема без синхронизации

class Counter {
    private int count = 0;
    
    public void increment() {
        count++; // НЕ атомарная операция!
    }
    
    public int getCount() {
        return count;
    }
}

// Из двух потоков инкрементируем 1000 раз каждый
// Результат будет < 2000, так как операции перемешиваются

Внутренне count++ выполняется как три операции:

  1. Прочитать текущее значение count
  2. Увеличить на 1
  3. Записать новое значение

Если два потока одновременно выполняют это, может быть race condition.

Решение: захватываем монитор

class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        // Поток 1: ждёт, пока поток 2 закончит
        // Потом выполняет
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// Теперь результат всегда 2000
// Операции выполняются одна за другой, не перемешиваются

Явное захватывание монитора разных объектов

class BankAccount {
    private double balance = 0;
    
    public void transfer(BankAccount to, double amount) {
        // Нужно синхронизировать оба счёта, чтобы избежать deadlock
        // Всегда захватываем в одинаковом порядке
        synchronized(this) {
            synchronized(to) {
                this.balance -= amount;
                to.balance += amount;
            }
        }
    }
}

Входы и выходы из монитора

public class MonitorExample {
    public synchronized void method1() {
        System.out.println("Вход в метод1"); // Монитор захвачен
        method2(); // Можно вызвать другой synchronized метод!
    }
    
    public synchronized void method2() {
        System.out.println("Вход в метод2"); // Тот же поток может захватить монитор повторно
    }
}

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

Состояния потока при работе с монитором

RUNNABLE (выполняется)
    ↓
synchronized(object) // Пытается захватить
    ↓
BLOCKED (ждёт в очереди)
    ↓
(монитор освободился, поток получил доступ)
    ↓
RUNNABLE (снова выполняется внутри блока)
    ↓
(выход из блока synchronized)
    ↓
RUNNABLE

Методы notify() и wait() работают только с монитором

class ProducerConsumer {
    private Queue<Integer> queue = new LinkedList<>();
    
    public synchronized void produce(int value) {
        queue.add(value);
        notifyAll(); // Пробуждает потоки, ждущие на ЭТОМ мониторе
    }
    
    public synchronized Integer consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // Освобождает монитор и ждёт notifyAll()
        }
        return queue.poll();
    }
}

Важно: wait() ОСВОБОЖДАЕТ монитор и ждёт, пока другой поток вызовет notify() или notifyAll() для того же монитора.

ReentrantLock — альтернатива монитору

Вместо встроенного монитора можно использовать явный lock:

import java.util.concurrent.locks.ReentrantLock;

class Counter {
    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();
        }
    }
}

Какой выбрать?

  • synchronized — простой, встроенный, правильно для большинства случаев
  • ReentrantLock — более гибкий, можно задать timeout, справедливое распределение

Потенциальные проблемы

Deadlock — взаимное ожидание:

// Поток 1: захватывает A, затем ждёт B
// Поток 2: захватывает B, затем ждёт A
// Оба ждут друг друга — deadlock

synchronized(a) {
    synchronized(b) {
        // ...
    }
}

Решение: всегда захватывайте мониторы в одинаковом порядке.

Вывод: захватить монитор означает получить исключительное право на объект, гарантируя, что только один поток может выполнять код внутри synchronized блока или метода одновременно. Это основа безопасности многопоточности в Java.

Что значит захватить монитор? | PrepBro