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

На что синхронизируешься при добавлении synchronized на метод?

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

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

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

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

На что синхронизируешься при добавлении synchronized на метод?

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

При добавлении synchronized на метод, ты синхронизируешься на объект (instance), если метод обычный, или на класс (Class объект), если метод static. Это означает, что в один момент времени только один поток может выполнять синхронизированный метод этого объекта.

Виды synchronized методов

// ✅ Instance synchronized method
public synchronized void instanceMethod() {
    // Синхронизируется на объект (this)
    // Только один поток может выполнять этот метод на одном объекте
}

// ✅ Static synchronized method
public static synchronized void staticMethod() {
    // Синхронизируется на Class объект (MyClass.class)
    // Только один поток может выполнять этот метод на весь класс
}

// ✅ Синхронизированный блок на объект
public void methodWithSyncBlock() {
    synchronized(this) {
        // Синхронизируется на объект (this)
    }
}

// ✅ Синхронизированный блок на класс
public void methodWithClassSync() {
    synchronized(MyClass.class) {
        // Синхронизируется на Class объект
    }
}

1. Instance synchronized method (синхронизация на объект)

Как это работает:

public class Counter {
    private int count = 0;
    
    // Синхронизируется на объект 'this'
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

public class Example {
    public static void main(String[] args) {
        Counter counter1 = new Counter();  // объект 1
        Counter counter2 = new Counter();  // объект 2
        
        // Разные объекты = разные мониторы (блокировки)
        // Поэтому эти методы могут выполняться одновременно:
        Thread t1 = new Thread(() -> counter1.increment());  // блокирует monitor объекта counter1
        Thread t2 = new Thread(() -> counter2.increment());  // блокирует monitor объекта counter2
        
        t1.start();
        t2.start();  // Оба выполнятся одновременно!
        
        // Но на одном объекте:
        Thread t3 = new Thread(() -> counter1.increment()); // хочет блокировку counter1
        Thread t4 = new Thread(() -> counter1.getCount());  // хочет блокировку counter1
        
        t3.start();
        t4.start();  // Один будет ждать другого!
    }
}

Визуализация:

Время 0:
Counter obj1 {           Counter obj2 {
  synchronized           synchronized
  monitor=LOCKED         monitor=FREE
  выполняет increment    готов к выполнению
}                        }

Время 1:
Counter obj1 {           Counter obj2 {
  synchronized           synchronized
  monitor=FREE           monitor=LOCKED
  готов                  выполняет increment
}                        }

Время 2:
Counter obj1 {           Counter obj2 {
  synchronized           synchronized
  monitor=LOCKED         monitor=FREE
  выполняет getCount     готов
}                        }

2. Static synchronized method (синхронизация на Class)

Как это работает:

public class Logger {
    private static int logCount = 0;
    
    // Синхронизируется на Logger.class
    public static synchronized void log(String message) {
        logCount++;
        System.out.println(message);
    }
    
    public static synchronized int getLogCount() {
        return logCount;
    }
}

public class Example {
    public static void main(String[] args) {
        Logger logger1 = new Logger();  // объект 1
        Logger logger2 = new Logger();  // объект 2
        
        // Разные объекты, но static synchronized синхронизируется на класс!
        // Поэтому они не могут выполняться одновременно:
        Thread t1 = new Thread(() -> logger1.log("Message 1"));  // хочет блокировку Logger.class
        Thread t2 = new Thread(() -> logger2.log("Message 2"));  // хочет блокировку Logger.class
        
        t1.start();
        t2.start();  // Один будет ждать другого! (даже на разных объектах)
        
        // Потому что есть только ОДНА блокировка на класс Logger
    }
}

Визуализация:

Vreme 0:
Logger.class {              logger1 {     logger2 {
  monitor=LOCKED            объект        объект
  выполняет log()                             
}                           }              }

Время 1:
Logger.class {              logger1 {     logger2 {
  monitor=LOCKED            объект        объект
  выполняет getLogCount()                    
}                           }              }

Время 2:
Logger.class {              logger1 {     logger2 {
  monitor=FREE              объект        объект
  готов                                      
}                           }              }

3. Разница между instance и static synchronized

public class BankAccount {
    private static int totalBalance = 0;  // Общий баланс всех счетов
    private int balance = 0;               // Баланс этого счета
    
    // Синхронизируется на объект (this)
    public synchronized void deposit(int amount) {
        balance += amount;  // Только этот счет блокируется
    }
    
    // Синхронизируется на класс
    public static synchronized void addToTotalBalance(int amount) {
        totalBalance += amount;  // Весь класс блокируется
    }
    
    public synchronized int getBalance() {
        return balance;
    }
    
    public static synchronized int getTotalBalance() {
        return totalBalance;
    }
}

public class Example {
    public static void main(String[] args) throws InterruptedException {
        BankAccount account1 = new BankAccount();
        BankAccount account2 = new BankAccount();
        
        // Эти два потока могут выполняться одновременно
        // (разные объекты, разные мониторы):
        Thread t1 = new Thread(() -> account1.deposit(100));
        Thread t2 = new Thread(() -> account2.deposit(200));
        
        // Но эти два потока NOT одновременно
        // (один класс, один монитор):
        Thread t3 = new Thread(() -> BankAccount.addToTotalBalance(100));
        Thread t4 = new Thread(() -> BankAccount.addToTotalBalance(200));
    }
}

Как работает монитор (Monitor)

Каждый объект и класс имеют встроенный монитор:

public class MonitorExample {
    private int value = 0;
    
    public synchronized void method1() {
        // Шаг 1: Поток пытается захватить монитор объекта
        // Шаг 2: Если монитор свободен — захватывает его и входит
        //        Если занят — ждёт в очереди
        value++;
        // Шаг 3: После выхода из метода монитор освобождается
    }
    
    public synchronized void method2() {
        // Этот метод также ждёт ТОГО ЖЕ монитора
        // Потому что синхронизируется на тот же объект
        value--;
    }
    
    public void unsynchronizedMethod() {
        // Этот метод НЕ требует монитор
        // Несколько потоков могут выполнять его одновременно
        System.out.println(value);
    }
}

Эквивалентность synchronized методов и блоков

// ✅ Эти два варианта ЭКВИВАЛЕНТНЫ для instance методов:

// Вариант 1: synchronized метод
public synchronized void instanceMethod() {
    // код
}

// Вариант 2: synchronized блок с 'this'
public void instanceMethod() {
    synchronized(this) {
        // код
    }
}

// ✅ Эти два варианта ЭКВИВАЛЕНТНЫ для static методов:

// Вариант 1: static synchronized метод
public static synchronized void staticMethod() {
    // код
}

// Вариант 2: synchronized блок с классом
public static void staticMethod() {
    synchronized(MyClass.class) {
        // код
    }
}

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

// ❌ Опасно: может привести к deadlock
public class BankTransfer {
    public synchronized void transferFrom(BankAccount other, int amount) {
        // Поток 1: заблокировал this, хочет заблокировать other
        other.withdraw(amount);
    }
    
    public synchronized void withdraw(int amount) {
        // Может ждать, пока this будет разблокирован
    }
}

// Сценарий deadlock'а:
// Поток 1: account1.transferFrom(account2, 100)
//   - блокирует account1
//   - хочет заблокировать account2 (вызов account2.withdraw)

// Поток 2: account2.transferFrom(account1, 100)
//   - блокирует account2
//   - хочет заблокировать account1 (вызов account1.withdraw)

// DEADLOCK! Оба потока ждут друг друга

// ✅ Решение: использовать synchronized блоки в одном порядке
public void transferFrom(BankAccount other, int amount) {
    BankAccount first, second;
    if (System.identityHashCode(this) < System.identityHashCode(other)) {
        first = this;
        second = other;
    } else {
        first = other;
        second = this;
    }
    
    synchronized(first) {
        synchronized(second) {
            // Теперь всегда блокируем в одном порядке
            this.balance -= amount;
            other.balance += amount;
        }
    }
}

Проверка синхронизации в Java

public class SyncCheck {
    public static void main(String[] args) {
        Counter counter = new Counter();
        
        // Проверяем, что объект может быть использован в synchronized
        System.out.println("Object hashCode: " + System.identityHashCode(counter));
        
        // Thread.holdsLock проверяет, есть ли у потока блокировка
        System.out.println("Holds lock: " + Thread.holdsLock(counter));
        
        Thread t = new Thread(() -> {
            synchronized(counter) {
                System.out.println("Inside sync: " + Thread.holdsLock(counter));
            }
            System.out.println("After sync: " + Thread.holdsLock(counter));
        });
        
        t.start();
    }
}

Таблица: что синхронизируется

ТипНа что синхронизируетсяОбласть влияния
synchronized void method()На объект (this)Только этот объект
static synchronized void method()На класс (Class)Весь класс
synchronized(this) { }На объект (this)Только блок
synchronized(obj) { }На объект (obj)Только блок
synchronized(MyClass.class) { }На класс (MyClass)Только блок

Заключение

При синхронизации на методе:

  1. Instance synchronized — синхронизируется на объект (this)

    • Разные объекты могут выполняться параллельно
    • Разные методы одного объекта не могут выполняться параллельно
  2. Static synchronized — синхронизируется на класс (MyClass.class)

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

  4. Deadlock — возможен, если неправильно комбинировать синхронизированные методы