← Назад к вопросам
Как Servlet работает в многопоточной среде
1.7 Middle🔥 131 комментариев
#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как Servlet работает в многопоточной среде
Архитектура Servlet контейнера
Servlet контейнер (Tomcat, Jetty и т.д.) использует следующую модель:
1 Servlet instance + многопоточность (Thread Pool)
↓
Каждый запрос = новый Thread
↓
Все потоки работают с одним объектом Servlet
Один инстанс Servlet — много потоков
Это критическое понимание:
@WebServlet("/user/*")
public class UserServlet extends HttpServlet {
// ОДИН инстанс
private UserService userService = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// Вызывается разными потоками одновременно
String userId = request.getParameter("id");
User user = userService.findUser(userId);
response.getWriter().println(user);
}
}
Когда приходят 1000 запросов одновременно:
- Создаётся ~1000 потоков (или переиспользуются из пула)
- Все 1000 потоков вызывают
doGet()на ОДНОМ объектеUserServlet - Это работает благодаря правильному использованию переменных
Проблема: shared state
Если в Servlet хранить состояние, возникают race conditions:
// НЕПРАВИЛЬНО - race condition!
public class BadServlet extends HttpServlet {
private String currentUser; // ОПАСНО!
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
currentUser = req.getParameter("user"); // Потоки перезаписывают друг другу
Thread.sleep(1000); // Дальше другой поток
System.out.println(currentUser); // Может вывести другого пользователя!
}
}
Сценарий:
- Поток A: currentUser = John
- Поток B: currentUser = Jane (переписал значение A)
- Поток A: выводит Jane вместо John
Правильный подход
// ПРАВИЛЬНО - используй локальные переменные
public class GoodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
String currentUser = req.getParameter("user"); // Stack variable
// Каждому потоку свой стек → свои переменные
User user = userService.findUser(currentUser);
res.getWriter().println(user);
}
}
Как работает многопоточность
Servlet Container имеет пул потоков. Каждый запрос обрабатывается отдельным потоком из пула, но все потоки работают с одним инстансом Servlet.
Жизненный цикл
public class UserServlet extends HttpServlet {
// ИНИЦИАЛИЗАЦИЯ (1 раз)
@Override
public void init() throws ServletException {
System.out.println("Servlet initialized");
// Запускается ОДИН раз при старте приложения
}
// ОБРАБОТКА ЗАПРОСА (много раз, разные потоки)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
// Вызывается МНОГОКРАТНО разными потоками
String id = req.getParameter("id");
// Каждый поток имеет свой стек с локальными переменными
}
// ДЕИНИЦИАЛИЗАЦИЯ (1 раз)
@Override
public void destroy() {
System.out.println("Servlet destroyed");
// Запускается ОДИН раз при остановке приложения
}
}
Threadsafety в Servlet
1. Локальные переменные БЕЗОПАСНЫ
Каждому потоку свой стек → нет конфликтов.
2. Полевые переменные (instance variables) ОПАСНЫ
public class UserServlet extends HttpServlet {
private UserService service; // Безопасно (immutable)
private String currentUser; // ОПАСНО!
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
currentUser = req.getParameter("user"); // Race condition!
}
}
3. HttpServletRequest и HttpServletResponse БЕЗОПАСНЫ
Безопасны - один на запрос.
Примеры потоковой безопасности
Неправильно
public class OrderServlet extends HttpServlet {
private int orderCounter = 0; // ОПАСНО!
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
orderCounter++; // Race condition
System.out.println("Order " + orderCounter);
}
}
Правильно
public class OrderServlet extends HttpServlet {
private final AtomicInteger orderCounter = new AtomicInteger(0);
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
int orderId = orderCounter.incrementAndGet(); // Потокобезопасно
System.out.println("Order " + orderId);
}
}
Или используй БД:
public class OrderServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
Order order = new Order();
orderService.save(order); // БД генерирует ID
}
}
Правило большого пальца
public class ProperServlet extends HttpServlet {
// Безопасно - immutable
private final UserService userService = new UserService();
private final Logger logger = LoggerFactory.getLogger(ProperServlet.class);
// Опасно - mutable state
private String currentUser; // Race condition
private List<Order> tempOrders; // Race condition
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
// Безопасно - локальные переменные
String userId = req.getParameter("id");
User user = userService.findUser(userId);
}
}
Spring MVC / REST Controllers
В современных приложениях работает точно так же:
@RestController
@RequestMapping("/api/users")
public class UserController {
// Безопасно - один инстанс, много потоков
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable String id) {
// Каждому потоку свои локальные переменные
User user = userService.findById(id);
return user;
}
}
Ключевые выводы
- Один Servlet инстанс обслуживает множество потоков
- Локальные переменные потокобезопасны (у каждого потока свой стек)
- Переменные экземпляра опасны (shared state → race conditions)
- HttpServletRequest/Response безопасны (один на запрос)
- Используй immutable объекты для полей Servlet
- Для счётчиков используй AtomicInteger или БД
- Современные фреймворки (Spring) следуют тем же принципам
Основное правило
Не сохраняй состояние в Servlet/Controller. Состояние должно быть в БД, HttpSession или ThreadLocal.