На каком уровне содержится монитор у нестатического метода c ключевым словом synchronized
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
На каком уровне содержится монитор у нестатического синхронизированного метода?
Краткий ответ
Монитор нестатического (instance) метода со словом synchronized содержится на уровне экземпляра объекта (instance). Синхронизация происходит на этом объекте (this).
Уровни мониторов в Java
В Java есть два уровня мониторов для synchronized методов:
- Instance level — монитор объекта (
this) - 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 method | this объект | 1 на объект | Простая синхронизация методов |
synchronized static | класс | 1 на класс | Статические ресурсы |
synchronized(obj) | объект | столько, сколько нужно | Гибкая синхронизация |
ReentrantLock | явная блокировка | с таймаутами | Сложные сценарии |
ConcurrentHashMap | segment-level | множество | Многопоточный доступ |
Итог
Нестатический synchronized метод синхронизирует на instance level (уровне объекта). Монитор содержится в object header каждого объекта. Разные объекты имеют разные мониторы, что позволяет разным потокам работать с разными объектами параллельно. Это в отличие от статических методов, которые синхронизируются на уровне класса, блокируя ВСЕ вызовы этого метода, независимо от объекта.