Ответ
Контейнер сервлетов (например, Tomcat) использует модель "один экземпляр — много потоков". Это означает, что для всех запросов к одному сервлету создаётся единственный экземпляр класса, но каждый запрос обрабатывается в отдельном потоке.
Архитектура и риски:
public class ConcurrentServlet extends HttpServlet {
// ОПАСНО: Это поле-экземпляр разделяется между всеми потоками (запросами)!
private int sharedCounter;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
sharedCounter++; // Race Condition (состояние гонки)!
// ... остальная логика
}
}
Проблема: Если несколько запросов одновременно вызовут doGet, операции чтения-изменения-записи sharedCounter могут перекрыться, что приведёт к потере данных.
Правильные подходы для потокобезопасности:
- Stateless-дизайн: Лучший способ. Не храните данные запроса в полях экземпляра. Используйте локальные переменные и параметры методов.
- Синхронизация: Используйте
synchronizedблоки для критических секций (но это снижает производительность). - Потокобезопасные классы: Для общего состояния используйте
AtomicInteger,ConcurrentHashMapизjava.util.concurrent. - 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-компоненты или корректно управлять общим состоянием.