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

Является ли Singleton Scope Bean потокобезопасным?

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

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

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

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

Является ли Singleton Scope Bean потокобезопасным?

Ответ: НЕТ, Singleton Scope Bean НЕ является автоматически потокобезопасным. Хотя Spring гарантирует, что вы получите тот же экземпляр Bean во всех местах использования, это не означает, что сам Bean потокобезопасный. Давайте разберемся в этом подробно.

Что такое Singleton Scope?

В Spring Framework есть несколько scope для Bean:

  • Singleton (по умолчанию) — один экземпляр на весь контекст приложения
  • Prototype — новый экземпляр при каждой инъекции
  • Request — один экземпляр на HTTP запрос
  • Session — один экземпляр на HTTP сессию
  • Application — один экземпляр на весь lifecycle приложения
@Component  //默认 Singleton Scope
@Scope(BeanScope.SINGLETON)  // Явное указание
public class UserService {
    private String sharedData;
    
    public void process() {
        // ...
    }
}

Проблема: состояние в Singleton Bean

Проблема возникает, когда Singleton Bean содержит mutable (изменяемое) состояние:

@Service
public class CounterService {
    private int counter = 0;  // ← ПРОБЛЕМА: mutable состояние
    
    public void increment() {
        counter++;  // ← Race condition в многопоточной среде!
    }
    
    public int getCounter() {
        return counter;
    }
}

В многопоточной среде (в веб-приложении есть много потоков) это приводит к race condition:

Поток 1: counter = 0
  ↓ прочитал counter (0)
Поток 2: counter = 0
  ↓ прочитал counter (0)
Поток 1: увеличил на 1, записал (1)
Поток 2: увеличил на 1, записал (1)  ← должно было быть 2!

Почему это опасно в веб-приложении?

Веб-приложение обслуживает многих пользователей одновременно. Каждый HTTP запрос обрабатывается отдельным потоком:

Пользователь 1 → Поток 1
Пользователь 2 → Поток 2  ← Используют ОДИН и тот же Singleton Bean
Пользователь 3 → Поток 3

Пример проблемы: UserService

@Service
public class UserService {
    private User currentUser;  // ← ПРОБЛЕМА!
    
    public void setCurrentUser(User user) {
        this.currentUser = user;
    }
    
    public User getCurrentUser() {
        return currentUser;
    }
    
    public void processUser() {
        User user = getCurrentUser();  // Может быть не тот пользователь!
        // ...
    }
}

В этом случае:

Поток 1 (User Alice): setCurrentUser(alice)
Поток 2 (User Bob):   setCurrentUser(bob)
Поток 1:              processUser() // ← Ожидал alice, но получил bob!

Как сделать Singleton Bean потокобезопасным?

1. Использовать immutable объекты

Лучший способ — избегать mutable состояния:

@Service
public class UserService {
    private final UserRepository repository;  // final и immutable
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public User getUserById(Long id) {
        return repository.findById(id);  // Не модифицируем состояние
    }
}

2. Использовать synchronized

Синхронизация гарантирует, что только один поток может выполнять метод одновременно:

@Service
public class CounterService {
    private int counter = 0;
    
    public synchronized void increment() {
        counter++;  // ← Теперь потокобезопасно
    }
    
    public synchronized int getCounter() {
        return counter;
    }
}

Но это может быть неэффективно (bottleneck).

3. Использовать AtomicInteger

Лучше использовать атомарные операции:

@Service
public class CounterService {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void increment() {
        counter.incrementAndGet();  // ← Потокобезопасно
    }
    
    public int getCounter() {
        return counter.get();
    }
}

4. Использовать ThreadLocal

Для хранения данных, специфичных для каждого потока:

@Service
public class UserContextService {
    private final ThreadLocal<User> userContext = new ThreadLocal<>();
    
    public void setCurrentUser(User user) {
        userContext.set(user);  // Каждый поток хранит свое значение
    }
    
    public User getCurrentUser() {
        return userContext.get();
    }
    
    public void clear() {
        userContext.remove();  // Важно очистить!
    }
}

5. Использовать Prototype Scope

Если Bean содержит состояние, может быть лучше использовать Prototype:

@Component
@Scope(BeanScope.PROTOTYPE)  // Новый экземпляр для каждого использования
public class RequestProcessor {
    private String data;
    
    public void process(String input) {
        this.data = input;  // Безопасно — у каждого потока своя копия
    }
}

Правильный пример: Singleton с dependency injection

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final PaymentService paymentService;
    private final NotificationService notificationService;
    
    public OrderService(
        OrderRepository orderRepository,
        PaymentService paymentService,
        NotificationService notificationService
    ) {
        this.orderRepository = orderRepository;
        this.paymentService = paymentService;
        this.notificationService = notificationService;
    }
    
    // Никакого mutable состояния — только зависимости (final)
    public void createOrder(OrderRequest request) {
        Order order = new Order(request);  // Локальная переменная
        orderRepository.save(order);
        paymentService.process(order);
        notificationService.notify(order);
    }
}

Этот Bean потокобезопасен, потому что:

  • Все зависимости — final (immutable)
  • Нет mutable поля на уровне класса
  • Каждый поток имеет свои локальные переменные

Практические рекомендации

  1. Избегай mutable состояния в Singleton Beans — это главное правило
  2. Используй dependency injection вместо хранения состояния в полях
  3. Если нужны переменные — делай их final и immutable
  4. Используй AtomicInteger/AtomicLong для простых счетчиков
  5. Используй ThreadLocal для данных, специфичных для потока
  6. Тестируй многопоточность — используй инструменты для стресс-тестирования

Заключение

Singleton Scope Bean в Spring НЕ является потокобезопасным по умолчанию. Потокобезопасность зависит от того, содержит ли Bean mutable состояние или нет. Лучшая практика — избегать любого состояния в Singleton Beans и использовать их только как контейнеры для хранения зависимостей через dependency injection. Если все поля — final и неизменяемые, то Bean будет потокобезопасным автоматически.

Является ли Singleton Scope Bean потокобезопасным? | PrepBro