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

Как 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); // Может вывести другого пользователя!
    }
}

Сценарий:

  1. Поток A: currentUser = John
  2. Поток B: currentUser = Jane (переписал значение A)
  3. Поток 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;
    }
}

Ключевые выводы

  1. Один Servlet инстанс обслуживает множество потоков
  2. Локальные переменные потокобезопасны (у каждого потока свой стек)
  3. Переменные экземпляра опасны (shared state → race conditions)
  4. HttpServletRequest/Response безопасны (один на запрос)
  5. Используй immutable объекты для полей Servlet
  6. Для счётчиков используй AtomicInteger или БД
  7. Современные фреймворки (Spring) следуют тем же принципам

Основное правило

Не сохраняй состояние в Servlet/Controller. Состояние должно быть в БД, HttpSession или ThreadLocal.

Как Servlet работает в многопоточной среде | PrepBro