Что значит захватить монитор?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что значит захватить монитор?
Захватить монитор (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++ выполняется как три операции:
- Прочитать текущее значение count
- Увеличить на 1
- Записать новое значение
Если два потока одновременно выполняют это, может быть 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.