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

Что является монитором для статического synchronized метода

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

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

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

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

Что является монитором для статического synchronized метода

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

Монитором для статического synchronized метода является объект Class (класс сам по себе), а не экземпляр класса. В Java каждый класс имеет свой Class объект, который служит монитором для синхронизации статических методов.

Различие между обычным и статическим synchronized

Обычный (instance) synchronized метод:

public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
        // Монитор: конкретный экземпляр this
    }
}

// Разные объекты - разные мониторы
Counter counter1 = new Counter();
Counter counter2 = new Counter();

// counter1.increment() и counter2.increment() могут выполняться параллельно
// Потому что они синхронизируются на разные объекты
thread1.start(() -> counter1.increment());
thread2.start(() -> counter2.increment());

Статический synchronized метод:

public class Counter {
    private static int count = 0;
    
    public static synchronized void increment() {
        count++;
        // Монитор: Counter.class (объект Class)
    }
}

// Всегда один и тот же монитор
// Counter.increment() и Counter.increment() НЕ могут выполняться параллельно
// Потому что они синхронизируются на один объект Counter.class
thread1.start(() -> Counter.increment());
thread2.start(() -> Counter.increment());

Объект Class как монитор

Что такое Class объект:

В Java каждый класс имеет свой объект Class, который создаётся загрузчиком классов:

public class MyClass {
    public static void test() {
        // Получить объект Class для MyClass
        Class<?> clazz1 = MyClass.class;
        Class<?> clazz2 = MyClass.class;
        
        // Это ОДИН и ТОТ ЖЕ объект
        System.out.println(clazz1 == clazz2);  // true
    }
}

// Также можно получить через getClass() на экземпляре
MyClass instance1 = new MyClass();
MyClass instance2 = new MyClass();

Class<?> clazz1 = instance1.getClass();
Class<?> clazz2 = instance2.getClass();

System.out.println(clazz1 == clazz2);  // true - это один объект Class

Практический пример

Статический synchronized монитор:

public class BankAccount {
    private static int totalBalance = 1000;
    
    // Это синхронизируется на BankAccount.class
    public static synchronized void transfer(int amount) {
        totalBalance -= amount;
        System.out.println("Переведено " + amount + ", остаток: " + totalBalance);
    }
}

// Использование:
class TransferThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            BankAccount.transfer(1);
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    // Создаём несколько потоков
    Thread t1 = new TransferThread();
    Thread t2 = new TransferThread();
    Thread t3 = new TransferThread();
    
    t1.start();
    t2.start();
    t3.start();
    
    t1.join();
    t2.join();
    t3.join();
    
    // Итоговый баланс: 700 (1000 - 300)
    // Это возможно ТОЛЬКО потому что все потоки синхронизируются на один монитор
}

Явное использование synchronized(class)

Вы можете явно синхронизироваться на объект Class:

public class Example {
    private static int counter = 0;
    
    // Способ 1: synchronized метод
    public static synchronized void increment1() {
        counter++;
    }
    
    // Способ 2: явно synchronized(Class)
    public static void increment2() {
        synchronized (Example.class) {
            counter++;
            // Эквивалент способу 1
        }
    }
    
    // Способ 3: синхронизация на getClass()
    public void increment3() {
        synchronized (this.getClass()) {
            counter++;  // Синхронизируется на Example.class
        }
    }
}

Сравнение мониторов

public class SyncExample {
    private int instanceVar = 0;
    private static int staticVar = 0;
    
    // 1. Обычный synchronized - монитор: this (текущий экземпляр)
    public synchronized void instanceMethod() {
        instanceVar++;
        // Монитор: this
    }
    
    // 2. Статический synchronized - монитор: SyncExample.class
    public static synchronized void staticMethod() {
        staticVar++;
        // Монитор: SyncExample.class
    }
    
    // 3. Явный монитор на this
    public void explicitInstance() {
        synchronized (this) {
            instanceVar++;
            // Эквивалент instanceMethod()
        }
    }
    
    // 4. Явный монитор на Class
    public static void explicitStatic() {
        synchronized (SyncExample.class) {
            staticVar++;
            // Эквивалент staticMethod()
        }
    }
}

// Визуализация:
// Instance method синхронизируется на: obj1, obj2, obj3 (разные мониторы)
// Static method синхронизируется на:   SyncExample.class (один монитор)

Важный пример: как это может привести к проблемам

public class Cache {
    private static Map<String, String> data = new HashMap<>();
    
    // ОПАСНО: если не осторожен с синхронизацией
    public void put(String key, String value) {
        // Это НЕ синхронизируется со статическим методом get()
        data.put(key, value);
        // Монитор: this (конкретный экземпляр)
    }
    
    public static synchronized String get(String key) {
        return data.get(key);
        // Монитор: Cache.class
    }
}

// ПРОБЛЕМА:
Cache cache1 = new Cache();
Cache cache2 = new Cache();

// Два разных монитора!
thread1.start(() -> cache1.put("key", "value"));  // Монитор: cache1
thread2.start(() -> Cache.get("key"));            // Монитор: Cache.class
// Race condition! HashMap не thread-safe!

Правильное решение

public class Cache {
    private static Map<String, String> data = new HashMap<>();
    
    // Вариант 1: оба статические synchronized
    public static synchronized void put(String key, String value) {
        data.put(key, value);  // Монитор: Cache.class
    }
    
    public static synchronized String get(String key) {
        return data.get(key);  // Монитор: Cache.class
    }
}

// Вариант 2: явная синхронизация на Class
public class Cache {
    private static Map<String, String> data = new HashMap<>();
    
    public void put(String key, String value) {
        synchronized (Cache.class) {
            data.put(key, value);
        }
    }
    
    public String get(String key) {
        synchronized (Cache.class) {
            return data.get(key);
        }
    }
}

// Вариант 3: использование ConcurrentHashMap (лучший вариант)
public class Cache {
    private static Map<String, String> data = new ConcurrentHashMap<>();
    
    public static void put(String key, String value) {
        data.put(key, value);
    }
    
    public static String get(String key) {
        return data.get(key);
    }
}

Таблица: Мониторы для разных типов synchronized

КодМониторВидимость
synchronized void method()thisНа все экземпляры
synchronized static void method()ClassName.classНа весь класс
synchronized(this) {}thisНа экземпляр
synchronized(ClassName.class) {}ClassName.classНа весь класс
synchronized(объект) {}Любой объектНа этот объект

Визуализация потоков

Два обычных synchronized метода:

threads: [T1] [T2] [T3] [T4]
objects: obj1  obj1  obj2  obj2
monitor: m1   m1   m2   m2
         |____|    |____|  <- Две независимые группы

Два статических synchronized метода:

threads: [T1] [T2] [T3] [T4]
objects: -    -    -    -    (неприменимо)
monitor: Class Class Class Class  (один монитор)
         |_________________________|  <- Все потоки конкурируют за один монитор

Заключение

Для статического synchronized метода монитором является объект Class самого класса. Это означает:

  • Все потоки, выполняющие статические synchronized методы одного класса, синхронизируются на один и тот же монитор
  • Статические synchronized методы конкурируют друг с другом за доступ
  • Это отличается от обычных synchronized методов, которые синхронизируются на каждый экземпляр отдельно
  • При необходимости вы можете явно синхронизировать код на Class объект, используя synchronized(ClassName.class)