Как сервлет (Servlet) обрабатывает конкурентные запросы в многопоточной среде?

«Как сервлет (Servlet) обрабатывает конкурентные запросы в многопоточной среде?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Контейнер сервлетов (например, Tomcat) использует модель "один экземпляр — много потоков". Это означает, что для всех запросов к одному сервлету создаётся единственный экземпляр класса, но каждый запрос обрабатывается в отдельном потоке.

Архитектура и риски:

public class ConcurrentServlet extends HttpServlet {
    // ОПАСНО: Это поле-экземпляр разделяется между всеми потоками (запросами)!
    private int sharedCounter;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        sharedCounter++; // Race Condition (состояние гонки)!
        // ... остальная логика
    }
}

Проблема: Если несколько запросов одновременно вызовут doGet, операции чтения-изменения-записи sharedCounter могут перекрыться, что приведёт к потере данных.

Правильные подходы для потокобезопасности:

  1. Stateless-дизайн: Лучший способ. Не храните данные запроса в полях экземпляра. Используйте локальные переменные и параметры методов.
  2. Синхронизация: Используйте synchronized блоки для критических секций (но это снижает производительность).
  3. Потокобезопасные классы: Для общего состояния используйте AtomicInteger, ConcurrentHashMap из java.util.concurrent.
  4. ThreadLocal: Для хранения данных, специфичных для одного запроса/потока (например, аутентификации).

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

public class SafeServlet extends HttpServlet {
    // Потокобезопасный счётчик
    private final AtomicInteger safeCounter = new AtomicInteger(0);

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        int localValue = safeCounter.incrementAndGet(); // Атомарная операция
        // Логика, использующая localValue...
        // Параметры запроса (req) и ответа (resp) уникальны для каждого потока.
    }
}

Ключевой вывод: Контейнер управляет многопоточностью, а разработчик должен проектировать сервлеты как stateless-компоненты или корректно управлять общим состоянием.