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

Будут ли два метода, помеченные как synchronized в контроллере-синглтоне, выполняться одновременно

2.3 Middle🔥 201 комментариев
#Spring Framework#Многопоточность

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

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

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

# Synchronized методы в синглтоне: будут ли выполняться одновременно

Ответ: НЕТ, не будут.

Это один из ключевых вопросов для понимания многопоточности в Java. Давай разберёмся подробно.

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

Если в синглтоне (объекте, существующем в одном экземпляре) два synchronized метода помечены как synchronized, они никогда не будут выполняться параллельно.

Она будут выполняться последовательно, так как оба заблокированы на одном и том же объекте (this).

Как работает synchronized

Механизм блокировки

@Component
public class UserController {
    
    public synchronized void method1() {
        System.out.println("Method 1 started");
        Thread.sleep(2000);  // Блокирует на 2 секунды
        System.out.println("Method 1 finished");
    }
    
    public synchronized void method2() {
        System.out.println("Method 2 started");
        Thread.sleep(2000);
        System.out.println("Method 2 finished");
    }
}

Когда один thread входит в synchronized метод:

Thread 1: Входит в method1() → получает монитор объекта this
          ↓
          Выполняет 2 секунды
          ↓
          Выходит из method1() → освобождает монитор
          
Thread 2: Ждёт, пока Thread 1 освободит монитор
          ↓
          Входит в method2()
          ↓
          Выполняет 2 секунды
          ↓
          Выходит из method2()

Результат выполнения

13:00:00 Method 1 started   (Thread 1)
13:00:02 Method 1 finished  (Thread 1)
13:00:02 Method 2 started   (Thread 2)  ← Ждал 2 секунды!
13:00:04 Method 2 finished  (Thread 2)

Общее время: 4 секунды (не параллельно, а последовательно)

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

@Component
public class PaymentController {
    private int balance = 1000;
    
    // Оба метода synchronlized на одном объекте
    public synchronized void withdraw(int amount) {
        System.out.println("Withdraw: Checking balance... " + balance);
        Thread.sleep(1000);  // Имитируем операцию с БД
        
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdraw: Removed " + amount + ", balance = " + balance);
        }
    }
    
    public synchronized void deposit(int amount) {
        System.out.println("Deposit: Adding " + amount + "...");
        Thread.sleep(1000);  // Имитируем операцию с БД
        
        balance += amount;
        System.out.println("Deposit: Added " + amount + ", balance = " + balance);
    }
}

// Использование
PaymentController controller = new PaymentController();  // Синглтон

new Thread(() -> controller.withdraw(200)).start();  // Thread 1
new Thread(() -> controller.deposit(300)).start();   // Thread 2

// Вывод:
// Withdraw: Checking balance... 1000
// Withdraw: Removed 200, balance = 800
// Deposit: Adding 300...
// Deposit: Added 300, balance = 1100
//
// НИКОГДА не будет:
// Withdraw: Checking balance... 1000
// Deposit: Adding 300...     ← Параллельно

Почему это важно?

Проблема Race Condition будет РЕШЕНА

// ❌ БЕЗ synchronized (Race Condition!)
public void transfer(int amount) {
    int balance = getBalance();           // Read 1000
    Thread.sleep(100);                    // Context switch!
    setBalance(balance - amount);         // Write 800
    
    // Если 2 потока одновременно
    // Оба прочитают 1000
    // Оба вычтут amount
    // Денег потеряется!
}

// ✅ С synchronized (Безопасно)
public synchronized void transfer(int amount) {
    int balance = getBalance();           // Только один thread
    Thread.sleep(100);                    // Другой ждёт
    setBalance(balance - amount);         // Безопасно
}

Визуализация времени выполнения

Без synchronized (БЕЗ потокобезопасности)

Время: 0ms ────────────────────────────── 4000ms

Thread 1 method1()  |━━━━━━━━━━━━━━━━━━━━━━━━━━|
Thread 2 method2()  |━━━━━━━━━━━━━━━━━━━━━━━━━━|
                     ↑                         ↑
                  Параллельно!        Закончилось всё в ~2000ms

С synchronized (ПОТОКОБЕЗОПАСНО)

Время: 0ms ────────────────────────────── 4000ms

Thread 1 method1()  |━━━━━━━━━━━━━━━━━━|
Thread 2 method2()  |                |━━━━━━━━━━━━━━━━━━|
                     ↑                ↑                   ↑
                  Первый         Второй ждёт       Закончилось в 4000ms

Что блокируется

Важно понимать: блокируется сам объект (монитор объекта), не код.

Вариант 1: Instance метод (блокирует this)

public synchronized void method1() {  // Блокирует this
    // ...
}

public synchronized void method2() {  // Блокирует тот же this
    // ...
}

// method1 и method2 конкурируют за тот же монитор

Вариант 2: Static метод (блокирует класс)

public static synchronized void staticMethod1() {  // Блокирует MyClass.class
    // ...
}

public static synchronized void staticMethod2() {  // Блокирует MyClass.class
    // ...
}

// Оба блокируют КЛАСС, не объект
// staticMethod1 и staticMethod2 также не выполняются параллельно

// НО staticMethod и обычный метод могут выполняться параллельно!
public synchronized void instanceMethod() {  // Блокирует this
    // Может выполняться параллельно со staticMethod
}

В Spring контексте

В Spring синглтон — это default scope:

@Component
// Это синглтон! Один объект на всё приложение
public class UserService {
    
    public synchronized void saveUser(User user) {
        // ...
    }
    
    public synchronized void updateUser(User user) {
        // ...
    }
}

// Spring создаёт ОДИН экземпляр:
UserService service = new UserService();

// Все потоки работают с ОДНИМ объектом
// Оба synchronized метода конкурируют за монитор этого объекта

Проблема: Производительность

@Component
public class ShoppingCart {
    
    public synchronized void addItem(Item item) {
        // Database insert — 100ms
    }
    
    public synchronized void removeItem(Item item) {
        // Database delete — 100ms
    }
    
    public synchronized void getItems() {
        // Database select — 50ms
    }
}

// Проблема:
// 100 concurrent users → все ждут в очереди
// Throughput падает
// Может быть bottleneck

Решение: Более лучка лучка granular locks

@Component
public class ShoppingCart {
    private final Object lock = new Object();
    
    public void addItem(Item item) {
        synchronized(lock) {  // Минимальный lock scope
            // Только критичная секция
            items.add(item);
        }
        // Остальное выполняется параллельно
    }
}

// Или ещё лучше — ReentrantLock или ConcurrentHashMap
@Component
public class ShoppingCart {
    private final ConcurrentHashMap<String, Item> items = new ConcurrentHashMap<>();
    
    public void addItem(String id, Item item) {
        items.put(id, item);  // Внутренне потокобезопасно, но менее коллизия
    }
}

Ответ на собеседовании

"Два synchronized метода в синглтоне не будут выполняться одновременно. Оба методы конкурируют за монитор одного объекта. Когда один thread входит в synchronized метод, другой получает lock, а остальные ждут. Это гарантирует потокобезопасность, но может быть проблемой для производительности. В таких случаях лучше использовать ReentrantLock, ConcurrentCollections или минимизировать критичную секцию."

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

СценарийПараллельно?Блокирует
Два synchronized instance методыНЕТМонитор объекта (this)
Два synchronized static методыНЕТМонитор класса (Class)
Synchronized instance + static методыДА!Разные мониторы
Synchronized + обычный методДА!Только synchronized
Разные объекты, оба synchronizedДА!Разные мониторы

Заключение

Два synchronized метода в одном синглтоне-объекте никогда не будут выполняться параллельно. Это гарантирует потокобезопасность, но требует тщательного планирования, чтобы избежать performance bottlenecks.

Будут ли два метода, помеченные как synchronized в контроллере-синглтоне, выполняться одновременно | PrepBro