Синхронизация в Java
Синхронизация нужна не «потокам вообще», а конкретному инварианту общего изменяемого состояния. Она решает три связанные задачи:
- взаимное исключение составной операции;
- видимость записей между потоками;
- порядок действий по правилам happens-before.
Инструменты
| Задача | Инструмент |
|---|---|
| защитить несколько полей одним инвариантом | synchronized или Lock |
| опубликовать независимое состояние/флаг | volatile |
| атомарно изменить одно значение | atomic-классы |
| передать элементы | BlockingQueue |
| ждать завершения N работ | CountDownLatch |
| встретить участников на повторяемом рубеже | CyclicBarrier |
| ограничить параллельный доступ | Семафор в Java |
| хранить потоконезависимые ключ-значение | ConcurrentHashMap |
final class Account {
private final Object lock = new Object();
private long balance;
void debit(long amount) {
synchronized (lock) {
if (amount > balance) throw new IllegalStateException();
balance -= amount;
}
}
}Одна и та же изменяемая сущность должна всегда защищаться одним согласованным механизмом. Если запись идет под lockA, а чтение под lockB, синхронизации между ними нет.
Lock и synchronized
synchronized автоматически освобождается при выходе и обычно проще. ReentrantLock нужен для tryLock, interruptible lock, fairness или нескольких Condition:
lock.lock();
try {
changeState();
} finally {
lock.unlock();
}Что синхронизацией не является
sleep()иyield();- надежда на порядок планировщика;
- потокобезопасная коллекция вокруг многошагового алгоритма без атомарного метода;
volatileдляcounter++;- объект lock, который каждый вызов создает заново.
См. Ключевое слово synchronized, Монитор в Java, Способы управления потоками java.util.concurrent.
На собеседовании
- Что дает synchronized кроме exclusive access? Видимость через unlock/последующий lock одного монитора.
- Можно ли защищать одно поле разными lock? Нет, между ними нет общей границы памяти.
- Когда volatile достаточно? Когда операция — независимое чтение/запись и нет составного инварианта.
- Почему concurrent-коллекция не делает весь алгоритм атомарным? Отдельные вызовы безопасны, их последовательность может гоняться.
- Чем Lock опаснее? Его нужно обязательно освободить вручную.