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

Потокобезопасный ли Scope Singleton

2.3 Middle🔥 151 комментариев
#Spring Framework

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

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

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

Потокобезопасный ли Scope Singleton

Краткий ответ: Нет, Singleton scope сам по себе НЕ гарантирует потокобезопасность. Singleton означает, что создается только один экземпляр бина, но это не защищает от race conditions при одновременном доступе из разных потоков.

Различие между Singleton и потокобезопасностью

public class SingletonVsThreadSafety {
    // Singleton: один экземпляр на все приложение
    // Потокобезопасность: безопасность при одновременном доступе
    // 
    // Это РАЗНЫЕ концепции!
    
    @Configuration
    public class AppConfig {
        @Bean
        @Scope("singleton")
        public UserService userService() {
            return new UserService();
        }
        // Этот бин создается один раз
        // Но много потоков могут одновременно
        // обращаться к его методам!
    }
}

Проблемы потокобезопасности в Singleton

1. Race condition при доступе к полям

public class UserService {
    // Это поле shared между всеми потоками!
    private List<User> cache = new ArrayList<>();
    
    public void addToCache(User user) {
        // Race condition!
        // Thread 1: читает size = 100
        // Thread 2: добавляет элемент
        // Thread 1: добавляет элемент с неправильным индексом
        cache.add(user);
    }
    
    public User getCachedUser(int index) {
        // Race condition!
        // Thread 1: проверяет index < size
        // Thread 2: удаляет элемент
        // Thread 1: выбрасывает IndexOutOfBoundsException
        return cache.get(index);
    }
}

public class ProblematicCode {
    public static void main(String[] args) 
            throws InterruptedException {
        
        UserService service = new UserService();
        
        // Два потока одновременно работают
        // с одним service
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                service.addToCache(
                    new User("User" + i));
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                service.addToCache(
                    new User("User" + (i + 1000)));
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        // Ожидали 2000 элементов
        // Получили < 2000 (некоторые потеряны)
    }
}

2. Visibility проблемы

public class VisibilityProblem {
    public class StateService {
        private boolean initialized = false;
        private String data = null;
        
        public void initialize() {
            data = "Initialized";
            initialized = true;
            // Memory visibility: не гарантируется!
        }
        
        public String getData() {
            if (initialized) {
                return data; // Может быть null!
            }
            return "Not initialized";
        }
    }
    
    // Решение: используй volatile или synchronized
    public class FixedStateService {
        private volatile boolean initialized = false;
        private volatile String data = null;
        
        public void initialize() {
            data = "Initialized";
            initialized = true;
            // volatile гарантирует видимость
        }
        
        public String getData() {
            if (initialized) {
                return data; // Гарантированно инициализировано
            }
            return "Not initialized";
        }
    }
}

3. Check-then-act race condition

public class CheckThenActRace {
    public class DataService {
        private Data data;
        
        public void processData() {
            // Race condition: check-then-act
            if (data != null) {
                // Между проверкой и использованием
                // другой поток может сделать data = null
                data.process(); // NullPointerException!
            }
        }
        
        public void clearData() {
            data = null;
        }
    }
    
    // Решение: синхронизация
    public class SafeDataService {
        private Data data;
        private final Object lock = new Object();
        
        public void processData() {
            synchronized (lock) {
                if (data != null) {
                    data.process();
                }
            }
        }
        
        public void clearData() {
            synchronized (lock) {
                data = null;
            }
        }
    }
}

Spring Singleton Scope и потокобезопасность

@Configuration
public class SpringSingletonThreadSafety {
    
    @Bean
    public UnsafeService unsafeService() {
        // Singleton, но небезопасный
        return new UnsafeService();
    }
    
    @Bean
    public SafeService safeService() {
        // Singleton и потокобезопасный
        return new SafeService();
    }
}

// НЕБЕЗОПАСНО
public class UnsafeService {
    private int counter = 0;
    
    public void increment() {
        counter++; // Race condition!
    }
    
    public int getCounter() {
        return counter;
    }
}

// БЕЗОПАСНО
public class SafeService {
    private final AtomicInteger counter = 
        new AtomicInteger(0);
    
    public void increment() {
        counter.incrementAndGet();
    }
    
    public int getCounter() {
        return counter.get();
    }
}

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

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

public class ImmutableService {
    private final ImmutableList<String> data;
    private final ImmutableMap<String, String> config;
    
    public ImmutableService(
            ImmutableList<String> data,
            ImmutableMap<String, String> config) {
        this.data = data;
        this.config = config;
    }
    
    public ImmutableList<String> getData() {
        return data; // Потокобезопасно
    }
    
    public ImmutableMap<String, String> getConfig() {
        return config; // Потокобезопасно
    }
}

@Configuration
public class ImmutableConfig {
    @Bean
    public ImmutableService immutableService() {
        return new ImmutableService(
            ImmutableList.of("data1", "data2"),
            ImmutableMap.of(
                "key1", "value1",
                "key2", "value2"
            )
        );
    }
}

2. Используй synchronized методы

public class SynchronizedService {
    private List<String> items = new ArrayList<>();
    
    public synchronized void addItem(
            String item) {
        items.add(item);
    }
    
    public synchronized String getItem(
            int index) {
        return items.get(index);
    }
    
    public synchronized int size() {
        return items.size();
    }
}

3. Используй atomic типы

public class AtomicService {
    private final AtomicInteger count = 
        new AtomicInteger(0);
    private final AtomicReference<String> status = 
        new AtomicReference<>("IDLE");
    
    public void increment() {
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
    
    public void setStatus(String newStatus) {
        status.set(newStatus);
    }
    
    public String getStatus() {
        return status.get();
    }
}

4. Используй ConcurrentCollections

public class ConcurrentService {
    private final ConcurrentHashMap<String, Integer> 
        metrics = new ConcurrentHashMap<>();
    private final CopyOnWriteArrayList<String> 
        logs = new CopyOnWriteArrayList<>();
    
    public void recordMetric(
            String key, int value) {
        metrics.put(key, value);
    }
    
    public int getMetric(String key) {
        return metrics.getOrDefault(key, 0);
    }
    
    public void addLog(String message) {
        logs.add(message);
    }
    
    public List<String> getLogs() {
        return new ArrayList<>(logs);
    }
}

5. Используй ReentrantLock

public class LockBasedService {
    private final ReentrantLock lock = 
        new ReentrantLock();
    private String state;
    
    public void updateState(String newState) {
        lock.lock();
        try {
            validateState(newState);
            state = newState;
        } finally {
            lock.unlock();
        }
    }
    
    public String getState() {
        lock.lock();
        try {
            return state;
        } finally {
            lock.unlock();
        }
    }
    
    private void validateState(
            String state) {
        if (state == null || 
            state.isEmpty()) {
            throw new IllegalArgumentException();
        }
    }
}

6. Используй ThreadLocal для thread-specific данных

public class ThreadLocalService {
    // Каждый поток имеет свою копию
    private static final ThreadLocal<Connection> 
        connectionHolder = new ThreadLocal<>();
    
    public Connection getConnection() {
        Connection conn = connectionHolder.get();
        if (conn == null) {
            conn = createConnection();
            connectionHolder.set(conn);
        }
        return conn;
    }
    
    public void closeConnection() {
        Connection conn = connectionHolder.get();
        if (conn != null) {
            try {
                conn.close();
            } finally {
                connectionHolder.remove();
            }
        }
    }
    
    private Connection createConnection() {
        // Создаем connection
        return null;
    }
}

Spring сам обеспечивает потокобезопасность?

@Service
public class UserService {
    @Autowired
    private UserRepository repository;
    // repository — Singleton
    // потокобезопасен? ДА!
    // Потому что это stateless сервис
    
    public User findById(Long id) {
        return repository.findById(id).orElse(null);
    }
}

public class ProblemService {
    private List<String> userNames = 
        new ArrayList<>(); // mutable state!
    
    public void processUser(String name) {
        userNames.add(name); // Race condition!
    }
}

Лучшие практики

@Configuration
public class ThreadSafeConfiguration {
    
    @Bean
    public CounterService counterService() {
        // Используй atomic типы
        return new CounterService();
    }
    
    @Bean
    public CacheService cacheService() {
        // Используй concurrent collections
        return new CacheService();
    }
    
    @Bean
    public StatelessService statelessService() {
        // Лучше всего: stateless service
        return new StatelessService();
    }
}

public class StatelessService {
    // Нет mutable state
    // Каждый метод: чистая функция
    // Автоматически потокобезопасен!
    
    public String process(String input) {
        String result = transform(input);
        return validate(result);
    }
    
    private String transform(String input) {
        return input.toUpperCase();
    }
    
    private String validate(String result) {
        return result.isEmpty() ? "EMPTY" : result;
    }
}

Вывод

Singleton scope НЕ гарантирует потокобезопасность. Один экземпляр на все приложение означает, что все потоки делят состояние. Для потокобезопасности нужно:

  1. Делай сервисы stateless (без mutable state)
  2. Используй immutable объекты
  3. Используй atomic типы для простых счетчиков
  4. Используй concurrent collections для работы с коллекциями
  5. Синхронизируй доступ к shared состоянию (synchronized, ReentrantLock)
  6. ThreadLocal для thread-specific данных

Это критически важно при разработке Spring приложений, где много потоков одновременно работают с одними и теми же бинами.

Потокобезопасный ли Scope Singleton | PrepBro