Почему не стоит использовать статические переменные для хранения счетчиков в многопоточном Java коде
Что проверяют
Понимаете ли вы обе проблемы: неатомарность и видимость.
Как отвечать
Статическое поле общее для всех потоков, поэтому счётчик на нём получает сразу два дефекта.
1. Увеличение не атомарно. Операция counter++ состоит из трёх шагов: прочитать, прибавить, записать. Два потока могут прочитать одно и то же значение и записать одинаковый результат, потеряв одно увеличение. При большом числе потоков теряется заметная доля.
2. Изменение может быть не видно. Без синхронизации у потока нет гарантии увидеть запись, сделанную другим. Значение может неопределённо долго читаться из кэша процессора.
Модификатор изменчивости чинит только вторую проблему. Он даёт видимость, но не делает операцию атомарной, поэтому для счётчика бесполезен. Это самая частая ошибка в ответах.
Что использовать:
- атомарный класс для счётчика: увеличение реализовано через операцию сравнения с обменом, без блокировок;
- при высокой конкуренции специализированный накопитель, разносящий значение по нескольким ячейкам и складывающий их только при чтении. Потоки перестают бороться за одну ячейку;
- синхронизация нужна, только если счётчик участвует в составной операции вместе с другими данными.
Отдельно стоит сказать про проблемы самой статики, помимо конкурентности:
- живёт до конца работы приложения, поэтому накопленное состояние никогда не освобождается;
- разделяется между всеми запросами в веб-приложении, а значит между всеми пользователями;
- мешает тестированию: значение переносится из теста в тест, и результат начинает зависеть от порядка выполнения;
- невозможно подменить заглушкой, потому что обращение идёт напрямую к классу.
Отсюда общий вывод: если счётчик действительно нужен глобально, он должен быть атомарным, а лучше вынесен в компонент с явным временем жизни или в систему метрик.
Где спотыкаются
Ставят модификатор изменчивости и считают проблему решённой.
Синхронизируют увеличение, но читают значение без синхронизации.
Не замечают, что состояние течёт между тестами.
Держат в статике не только счётчики, но и кэши, получая утечку.