Конкурентность
Потоки на JVM делят общую память, и вся сложность — в согласовании доступа к ней. Kotlin здесь опирается на модель JVM: монитор (synchronized) даёт взаимное исключение, wait/notify — координацию, а модель памяти Java (JMM) описывает, что один поток гарантированно видит из записей другого. Забыть про любой из этих слоёв — значит получить гонку данных, взаимоблокировку или невидимую запись.
Коварство в том, что такие баги недетерминированы: код может годами «работать», пока смена железа или нагрузки не изменит тайминг. Поэтому конкурентность понимают не по принципу «сработало», а по правилам — что гарантирует монитор, что гарантирует @Volatile, когда возникает deadlock и почему объект утекает, оставаясь достижимым от GC-корня. Полная карта — в слоях ниже.
Карта темы
- Монитор и synchronized — взаимное исключение через монитор объекта и что защищает
synchronized. - wait и notify — как потоки координируются, отпуская монитор в ожидании.
- Взаимоблокировка (deadlock) — четыре условия дедлока и как два потока замирают навсегда.
- Порядок захвата локов — единый глобальный порядок как способ исключить deadlock.
- Модель памяти Java — почему запись одного потока может быть не видна другому и допустим вывод
[0, 0]. - Отношение happens-before — что даёт гарантию видимости и упорядочивания.
- Видимость и volatile — что
@Volatileгарантирует, а что нет. - Утечки памяти — достижимость от GC-корня,
inner-классы иWeakReference.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Думать, что synchronized отпускает монитор между итерациями цикла | Поток держит монитор весь цикл — второй голодает, фактический deadlock |
| Захватывать два лока в разном порядке в разных потоках | Круговое ожидание и взаимоблокировка |
| Считать, что запись видна другим потокам мгновенно | Без happens-before запись может быть не видна вовсе |
Верить, что @Volatile делает count++ атомарным | @Volatile даёт видимость, но не атомарность — обновление теряется |
Винить WeakReference вместо неявной ссылки inner-класса | Настоящая утечка — сильная ссылка this$0 на внешний объект |
| Ждать, что планировщик ОС сам разорвёт deadlock | Deadlock вечен — его нужно исключать порядком локов |
Значение для собеседований
Конкурентность — любимая тема для отсева: она показывает, отличаете ли вы «видимость» от «атомарности», монитор от лока, и понимаете ли, что баг может быть недетерминированным. Кандидат, который говорит «@Volatile — про видимость, synchronized — про взаимное исключение, deadlock — про круговое ожидание», сразу опережает того, кто валит всё в «ну, там гонка».
Что обычно проверяют:
- Что защищает
synchronizedи почему монитор держится весь блок. - Как
wait/notifyкоординируют потоки и почему их зовут под монитором. - Четыре условия deadlock и как единый порядок локов его исключает.
- Что гарантирует
@Volatile(видимость) и что нет (атомарностьcount++). - Почему возможен вывод
[0, 0]без барьеров и что такое happens-before. - Почему объект утекает, оставаясь достижимым, и чем помогает
WeakReference.
Типичный неверный ответ: «@Volatile делает операции атомарными, поэтому счётчик потокобезопасен». На деле @Volatile гарантирует только видимость и порядок, но count++ — это чтение-инкремент-запись, и два потока всё равно теряют обновление; для атомарности нужен synchronized или AtomicInteger.