Является ли Spring Boot приложение однопоточным или многопоточным по умолчанию
Что проверяют
Понимаете ли вы, кто и где исполняет ваш код. Короткого «многопоточное» мало: ждут объяснения модели, кто создаёт потоки, сколько их и какие требования из этого следуют для ваших бинов. Обычно вопрос ведёт к гонкам в состоянии синглтона, поэтому отвечать имеет смысл сразу в эту сторону.
Как отвечать
По умолчанию приложение многопоточное. Встроенный контейнер (Tomcat, если его не заменили на Jetty или Undertow) держит пул рабочих потоков, и каждый HTTP-запрос обрабатывается целиком в одном потоке пула: поток на запрос. Верхняя граница по умолчанию двести потоков, меняется через server.tomcat.threads.max.
Отсюда главное следствие. Бины по умолчанию синглтоны, то есть один экземпляр сервиса живёт во всех этих потоках сразу. Изменяемое поле в нём это общая память и гонка; состояние держат в аргументах метода, в запросной области видимости или во внешнем хранилище.
Назовите и остальные потоки: @Async работает на отдельном исполнителе, планировщик @Scheduled по умолчанию однопоточный, поэтому долгая задача задержит следующую, свои пулы есть у HTTP-клиентов и драйверов базы. Привязанный к потоку запроса контекст (безопасность, транзакция, запросные бины) в асинхронную задачу сам не переезжает.
Отдельно стоит WebFlux: там немного потоков событийного цикла, и блокирующий вызов внутри обработчика тормозит не один запрос, а весь цикл. В современных версиях включаются виртуальные потоки, после чего поток на запрос перестаёт быть дорогим.
Где спотыкаются
Отвечают, что приложение однопоточное, раз запускается из main. Из main стартует контейнер и заканчивается только инициализация, дальше запросы обслуживают потоки пула.
Держат состояние в поле синглтона: счётчик, текущего пользователя, накопленный список. Локально и в тестах всё сходится, а под нагрузкой данные одного запроса утекают в ответ другому.
Считают, что аннотации работают сами по себе. Вызов метода с @Async или @Transactional изнутри того же бина идёт мимо прокси, и код выполняется синхронно в текущем потоке, без обещанного эффекта.