Какие проблемы могут возникнуть при одновременном вызове метода из нескольких потоков в Java
Что проверяют
Понимаете ли вы, что беда не только в гонке за инкрементом, но и в видимости изменений между потоками, то есть в модели памяти Java. Хороший ответ перечисляет классы проблем, а не один пример со счётчиком. Дальше обычно просят починить конкретный код и рассказать, как такую ошибку воспроизвести.
Как отвечать
- гонка (race condition):
count++это три операции, чтение, инкремент и запись, между ними успевает вклиниться другой поток; - потеря видимости: без
volatileили синхронизации поток может долго не видеть чужую запись: значение живёт в регистре, а JIT вправе вынести чтение из цикла; - неатомарность составной операции:
if (map.get(k) == null) map.put(k, v)небезопасна даже наConcurrentHashMap, нуженputIfAbsentилиcomputeIfAbsent; - взаимная блокировка: два лока захвачены в разном порядке. Лечится единым порядком захвата или
tryLockс таймаутом уReentrantLock; - голодание и ливлок: поток формально жив, но полезной работы не делает;
- публикация недостроенного объекта: ссылка на
thisутекает из конструктора, и другой поток видит поля со значениями по умолчанию. Поляfinalдают гарантию публикации; - порча небезопасных структур:
HashMapпод конкурентной записью в Java 7 зацикливался при расширении, в современных версиях просто теряет записи.
Инструменты: synchronized и ReentrantLock, volatile для флагов, AtomicInteger и LongAdder под высокой конкуренцией, иммутабельность и record, коллекции из java.util.concurrent, ExecutorService вместо ручных потоков. С Java 21 в ходу виртуальные потоки, а в Java 24 сняли закрепление несущего потока на synchronized, поэтому блокирующий код в них перестал быть проблемой.
Где спотыкаются
Помнят только гонку и не знают про видимость. На вопрос «а если поле читается в цикле без синхронизации» отвечают, что рано или поздно обновление увидят, хотя гарантии нет.
Считают volatile заменой синхронизации. Он даёт видимость и запрет переупорядочивания, но не делает i++ атомарным.
Пишут тест в три потока, он проходит, и на этом успокаиваются. Гонка воспроизводится редко, для проверки нужны стресс-прогон, jcstress или анализ кода.