Как работает синхронизация потоков в Java
Что проверяют
Понимаете ли вы, что синхронизация решает две задачи сразу, взаимное исключение и видимость, и знаете ли инструменты кроме synchronized.
Как отвечать
Проблема возникает там, где несколько потоков работают с общим изменяемым состоянием. Нужны две гарантии: атомарность (никто не влезет в середину операции) и видимость (изменение одного потока увидят остальные, а не старое значение из кэша).
Инструменты по возрастанию:
- volatile: только видимость. Годится для флага, который пишут и читают, но не для счётчика;
- атомарные классы (
AtomicInteger,AtomicReference): атомарность без блокировок через compare-and-swap. Поток не засыпает, а повторяет попытку; - synchronized: блок или метод, монитор объекта. Даёт и атомарность, и видимость. Вход в блок и выход из него это те самые точки, где память синхронизируется;
- ReentrantLock: то же самое, но с попыткой захвата по таймауту, прерыванием, честностью и несколькими условиями через
Condition; - ReadWriteLock и StampedLock: много читателей, редкие писатели;
- высокоуровневое:
CountDownLatch,CyclicBarrier,Semaphore,Phaserдля координации, конкурентные коллекции вместо ручных блокировок.
Правило, которое стоит произнести: лучший способ синхронизации это её отсутствие. Неизменяемые объекты, локальные переменные и передача данных через очередь снимают проблему целиком, вместо того чтобы её решать.
Где спотыкаются
Считают volatile заменой synchronized.
Синхронизируются на this в публичном классе, из-за чего чужой код может заблокировать тот же монитор.
Захватывают две блокировки в разном порядке и получают взаимную блокировку.
Держат блокировку на время сетевого вызова.