Почему не рекомендуется использовать нестатические и нефинальные поля в сервлете?

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

Ответ

Использование нестатических и нефинальных полей в сервлете крайне не рекомендуется из-за проблем с потокобезопасностью. Контейнер сервлетов использует один экземпляр сервлета для обработки всех запросов, и несколько потоков могут одновременно читать и изменять такое поле, что приводит к состоянию гонки (race condition) и некорректным данным.

Опасный пример:

public class CounterServlet extends HttpServlet {
    private int count; // Нестатическое, нефинальное поле — разделяемое состояние!

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        count++; // Операция не атомарна: чтение-изменение-запись
        resp.getWriter().write("Count: " + count);
    }
}

Правильные подходы:

  1. Использовать локальные переменные. Они создаются в стеке каждого потока и безопасны.
  2. Использовать потокобезопасные классы из java.util.concurrent.

    private final AtomicInteger safeCounter = new AtomicInteger(0);
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        int current = safeCounter.incrementAndGet(); // Атомарная операция
        // ...
    }
  3. Использовать ThreadLocal для хранения состояния, специфичного для потока (например, идентификатор пользовательской сессии).
  4. Использовать синхронизацию (synchronized) — но это может стать узким местом для производительности.
  5. Объявлять поля как final, если они неизменяемы и инициализируются при создании сервлета.

Вывод: Состояние сервлета должно быть либо неизменяемым (final), либо управляться через потокобезопасные механизмы, либо избегаться вовсе.