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

На каком уровне содержится монитор у нестатического метода c ключевым словом synchronized

1.8 Middle🔥 121 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate

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

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

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

На каком уровне содержится монитор у нестатического синхронизированного метода?

Краткий ответ

Монитор нестатического (instance) метода со словом synchronized содержится на уровне экземпляра объекта (instance). Синхронизация происходит на этом объекте (this).

Уровни мониторов в Java

В Java есть два уровня мониторов для synchronized методов:

  1. Instance level — монитор объекта (this)
  2. Class level — монитор класса (Class объект)
public class Monitor {
    private int value = 0;
    
    // Уровень 1: Instance Monitor (монитор объекта)
    public synchronized void instanceMethod() {
        // Синхронизация на: this.monitor
        // Если два потока вызывают эот метод на РАЗНЫХ объектах - исполнятся параллельно
        value++;
    }
    
    // Уровень 2: Class Monitor (монитор класса)
    public static synchronized void staticMethod() {
        // Синхронизация на: Monitor.class.monitor
        // Синхронизирует ВСЕ вызовы этого метода, независимо от объекта
    }
    
    // Явная синхронизация на объекте (эквивалент instanceMethod)
    public void instanceMethodExplicit() {
        synchronized (this) {
            // Эквивалентно synchronized instanceMethod()
            value++;
        }
    }
    
    // Явная синхронизация на классе (эквивалент staticMethod)
    public void classMonitorExplicit() {
        synchronized (Monitor.class) {
            // Эквивалентно synchronized static method
        }
    }
}

Instance Monitor в деталях

Монитор содержится В ОБЪЕКТЕ

Каждый объект в Java имеет встроенный monitor (intrinsic lock). Это не отдельное поле, а часть объектного заголовка (object header) в памяти.

public class BankAccount {
    private double balance = 1000.0;
    
    // synchronized методы
    public synchronized void withdraw(double amount) {
        // Для снятия денег - синхронизируемся на this
        balance -= amount;
    }
    
    public synchronized void deposit(double amount) {
        // Для пополнения - синхронизируемся на this
        balance += amount;
    }
    
    public synchronized double getBalance() {
        return balance;
    }
}

// Использование
BankAccount account = new BankAccount();  // Объект с встроенным monitor

// Поток 1
Thread t1 = new Thread(() -> account.withdraw(100));

// Поток 2  
Thread t2 = new Thread(() -> account.deposit(200));

t1.start();
t2.start();

// Что происходит:
// Поток 1: хочет захватить monitor объекта account
// Поток 2: хочет захватить monitor объекта account
// Один из них входит в synchronized блок
// Другой ждёт, пока первый выйдет

Объектный заголовок (Object Header)

В памяти Java объект хранится так:

┌─────────────────────────────────────┐
│       Object Header (12-16 bytes)   │
├─────────────────────┬───────────────┤
│  Mark Word (8 bytes)│ Klass Word    │  
│  (contains monitor) │ (type info)   │
├─────────────────────┴───────────────┤
│       Field Data                    │
│   - balance: 1000.0                 │
│   - ...                             │
└─────────────────────────────────────┘

Mark Word содержит информацию о мониторе:

  • Кто владеет монитором (потокID)
  • Количество захватов (reentrant count)
  • Хеш код объекта
  • Возраст объекта (для GC)
public class MonitorStructure {
    private String data;
    
    // Когда поток входит в synchronized блок
    public synchronized void lockExample() {
        // Mark Word теперь содержит:
        // - ID потока владельца
        // - reentrant count = 1
        
        // Если ты вызовешь другой synchronized метод того же объекта
        lockExample2();
        // reentrant count = 2
        // После выхода из lockExample2: reentrant count = 1
        // После выхода из lockExample: reentrant count = 0, монитор свободен
    }
    
    public synchronized void lockExample2() {
        // Тот же поток - не блокируется, увеличиваем reentrant count
    }
}

Instance vs Static Monitor

public class MonitorComparison {
    private int value = 0;
    static int staticValue = 0;
    
    // Instance Monitor - синхронизирует на ЭТОМ объекте
    public synchronized void instanceLocked() {
        // synchronized (this) {
        value++;
        // }
    }
    
    // Static Monitor - синхронизирует на КЛАССЕ
    public static synchronized void staticLocked() {
        // synchronized (MonitorComparison.class) {
        staticValue++;
        // }
    }
}

// Демонстрация разницы
MonitorComparison obj1 = new MonitorComparison();
MonitorComparison obj2 = new MonitorComparison();

Thread t1 = new Thread(() -> obj1.instanceLocked());  // Монитор obj1
Thread t2 = new Thread(() -> obj2.instanceLocked());  // Монитор obj2 - ДРУГОЙ!

t1.start();
t2.start();
// Они исполняются ПАРАЛЛЕЛЬНО - разные мониторы!

Thread t3 = new Thread(() -> MonitorComparison.staticLocked());    // Монитор класса
Thread t4 = new Thread(() -> MonitorComparison.staticLocked());    // Тот же монитор класса

t3.start();
t4.start();
// Они исполняются ПОСЛЕДОВАТЕЛЬНО - один монитор на класс!

Иерархия мониторов

public class MonitorHierarchy {
    private int value = 0;
    
    // Монитор 1: объекта this
    public synchronized void method1() {
        value++;
        method2();  // Реентрантный вызов - тот же монитор
    }
    
    // Монитор 1: объекта this (тот же что method1)
    public synchronized void method2() {
        value++;
    }
    
    // Монитор 2: другого объекта
    private final Object lockObject = new Object();
    
    public void method3() {
        synchronized (lockObject) {
            // Монитор lockObject
            value++;
        }
    }
    
    // Эквивалентно method1
    public void method1Explicit() {
        synchronized (this) {
            value++;
            synchronized (this) {  // Реентрантный - работает
                value++;
            }
        }
    }
}

MonitorHierarchy obj = new MonitorHierarchy();

// Вызов method1
// 1. Получи монитор this
// 2. Выполни value++
// 3. Вызови method2
//    3.1 Попытка получить монитор this (уже владеешь!)
//    3.2 Reentrant count++
//    3.3 Выполни value++
//    3.4 Выход - reentrant count--
// 4. Выход - освободи монитор

Мониторы и видимость данных (Memory Visibility)

public class MemoryVisibility {
    private int value = 0;  // НЕ volatile!
    
    // Instance Monitor гарантирует видимость
    public synchronized void setValue(int v) {
        value = v;  // изменение видимо другим потокам
        // При выходе из synchronized - memory barrier
    }
    
    public synchronized int getValue() {
        return value;  // гарантированно читаем свежее значение
        // При входе в synchronized - memory barrier
    }
    
    // Это работает потому что:
    // synchronized = mutual exclusion + memory visibility
}

// Эквивалентно с явными мониторами
public class ExplicitMonitor {
    private int value = 0;
    
    public void setValue(int v) {
        synchronized (this) {
            value = v;  // видимо
        }  // memory barrier при выходе
    }
    
    public int getValue() {
        synchronized (this) {  // memory barrier при входе
            return value;  // видимо
        }
    }
}

Deadlock - опасность с множественными мониторами

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    private int value1 = 0, value2 = 0;
    
    // Нестатический метод 1
    public void transferMoney1To2() throws InterruptedException {
        synchronized (lock1) {
            System.out.println("Got lock1");
            Thread.sleep(100);  // Пусть другой поток заблокирует lock2
            synchronized (lock2) {
                value1--;
                value2++;
            }
        }
    }
    
    // Нестатический метод 2
    public void transferMoney2To1() throws InterruptedException {
        synchronized (lock2) {
            System.out.println("Got lock2");
            Thread.sleep(100);  // Пусть другой поток заблокирует lock1
            synchronized (lock1) {
                value2--;
                value1++;
            }
        }
    }
}

// Deadlock
DeadlockExample account = new DeadlockExample();

Thread t1 = new Thread(() -> {
    try { account.transferMoney1To2(); }
    catch (InterruptedException e) {}
});

Thread t2 = new Thread(() -> {
    try { account.transferMoney2To1(); }
    catch (InterruptedException e) {}
});

t1.start();
t2.start();
// t1: захватил lock1, ждёт lock2
// t2: захватил lock2, ждёт lock1
// DEADLOCK!

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

1. Синхронизируй на минимальном уровне

public class BadPractice {
    private int count = 0;
    
    // ❌ Весь метод синхронизирован
    public synchronized void slowMethod() {
        doSlowIO();  // медленная операция
        count++;     // критическая секция
        doMoreIO();  // ещё одна медленная операция
    }
}

public class GoodPractice {
    private int count = 0;
    private final Object lock = new Object();
    
    // ✓ Синхронизируй только критическую секцию
    public void fastMethod() {
        doSlowIO();  // без синхронизации
        synchronized (lock) {
            count++;  // критическая секция
        }
        doMoreIO();  // без синхронизации
    }
}

2. Предпочитай Concurrent Collections

public class BadConcurrency {
    private Map<String, Integer> map = new HashMap<>();
    
    // ❌ Сложно управлять мониторами
    public synchronized void put(String key, int value) {
        map.put(key, value);
    }
    
    public synchronized int get(String key) {
        return map.getOrDefault(key, 0);
    }
}

public class GoodConcurrency {
    // ✓ ConcurrentHashMap имеет встроенную синхронизацию
    private Map<String, Integer> map = new ConcurrentHashMap<>();
    
    public void put(String key, int value) {
        map.put(key, value);
    }
    
    public int get(String key) {
        return map.getOrDefault(key, 0);
    }
}

3. Используй ReentrantLock для сложных сценариев

public class BetterLocking {
    private final ReentrantLock lock = new ReentrantLock();
    private int value = 0;
    
    // Больше контроля чем synchronized
    public void method() {
        lock.lock();
        try {
            value++;
            // ...
        } finally {
            lock.unlock();
        }
    }
    
    // С таймаутом (synchronized не поддерживает)
    public boolean tryMethod(long timeout, TimeUnit unit) {
        if (lock.tryLock(timeout, unit)) {
            try {
                value++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
}

Итоговая таблица

ТипСинхронизируетМониторовКогда использовать
synchronized methodthis объект1 на объектПростая синхронизация методов
synchronized staticкласс1 на классСтатические ресурсы
synchronized(obj)объектстолько, сколько нужноГибкая синхронизация
ReentrantLockявная блокировкас таймаутамиСложные сценарии
ConcurrentHashMapsegment-levelмножествоМногопоточный доступ

Итог

Нестатический synchronized метод синхронизирует на instance level (уровне объекта). Монитор содержится в object header каждого объекта. Разные объекты имеют разные мониторы, что позволяет разным потокам работать с разными объектами параллельно. Это в отличие от статических методов, которые синхронизируются на уровне класса, блокируя ВСЕ вызовы этого метода, независимо от объекта.