Конкурентность
Потоки, мониторы synchronized, взаимоблокировки, модель памяти Java и утечки памяти.
6 вопросов
JuniorТеорияОчень частоЧто гарантирует @Volatile, и что он НЕ гарантирует?
Что гарантирует @Volatile, и что он НЕ гарантирует?
@Volatile гарантирует видимость — запись в поле сразу видна другим потокам, а чтения/записи не переупорядочиваются вокруг него (ребро happens-before). Но составные действия вроде count++ он атомарными НЕ делает: два потока могут потерять обновление.
Типичные ошибки
- ✗Считать, что
@Volatileделаетcount++атомарным между потоками - ✗Думать, что
@Volatileдаёт взаимное исключение, как лок - ✗Полагать, что видимость влечёт атомарность составных операций
Уточняющие вопросы
- →Какой класс использовать, чтобы сделать
count++атомарным вместо@Volatile? - →Что такое отношение happens-before в модели памяти Java?
JuniorДебаггингЧастоПочему ни один поток не закончит, когда два лока берутся в обратном порядке?
Почему ни один поток не закончит, когда два лока берутся в обратном порядке?
Поток 1 держит cup и ждёт coffeeMaker; поток 2 держит coffeeMaker и ждёт cup. Каждый ждёт лок, который держит другой, — это deadlock, никто не продвигается. Решение — брать оба лока в едином глобальном порядке во всех потоках.
Типичные ошибки
- ✗Считать, что реентерабельность
synchronizedспасает от deadlock на двух объектах - ✗Думать, что планировщик ОС сам со временем разорвёт deadlock
- ✗Полагать, что разный порядок локов в потоках безвреден
Уточняющие вопросы
- →Какие четыре условия должны выполняться одновременно для возникновения deadlock?
- →Как
tryLockс таймаутом мог бы избежать зависания вместо фиксированного порядка?
JuniorТеорияЧастоЧто такое утечка памяти на JVM, и чем помогает WeakReference?
Что такое утечка памяти на JVM, и чем помогает WeakReference?
Утечка — это объект, который программе больше не нужен, но остаётся достижимым от GC-корня, поэтому сборщик мусора его не освобождает. WeakReference ссылается на объект, не удерживая его живым: GC может его освободить, в отличие от сильной ссылки.
Типичные ошибки
- ✗Приравнивать утечку к мгновенному
OutOfMemoryError, а не к медленному росту достижимого - ✗Думать, что JVM использует подсчёт ссылок, а не обход достижимости
- ✗Считать, что
WeakReferenceсобирается немедленно при каждом чтении
Уточняющие вопросы
- →Чем
WeakReferenceотличается отSoftReference? - →Как трассирующий сборщик мусора решает, что объект — мусор?
MiddleДебаггингИногдаПочему этот producer/consumer с busy-loop под synchronized встаёт?
Почему этот producer/consumer с busy-loop под synchronized встаёт?
Тот поток, что первым вошёл в synchronized(phone), держит монитор весь свой бесконечный цикл и никогда не отпускает его, поэтому другой вечно блокируется — фактически deadlock. Решение — wait/notify: поток зовёт phone.wait(), чтобы отпустить монитор в простое, а другой зовёт notify() после изменения состояния.
Типичные ошибки
- ✗Думать, что
synchronizedотпускает монитор между итерациями цикла - ✗Винить гонку данных вместо никогда не отпускаемого монитора
- ✗Считать, что один
@Volatileпозволит двум busy-loop'ам кооперироваться
Уточняющие вопросы
- →Почему
waitиnotifyдолжны вызываться при удержании монитора объекта? - →Как
waitотпускает монитор, а busywhile-цикл — нет?
MiddleДебаггингИногдаПочему этот Activity всё равно течёт после поворота, несмотря на WeakReference?
Почему этот Activity всё равно течёт после поворота, несмотря на WeakReference?
inner class держит неявную сильную ссылку на внешний MainActivity (this$0), а работающий Thread удерживает Job, — поэтому старый Activity остаётся достижим, а WeakReference тут отвлекающий манёвр. Решение — сделать Job nested (не inner) классом.
Типичные ошибки
- ✗Винить
WeakReferenceвместо неявной ссылкиinner-класса на внешний объект - ✗Думать, что
innerне несёт ссылки на свой внешний класс - ✗Переходить на
SoftReferenceвместо разрыва сильной внешней связи
Уточняющие вопросы
- →В чём разница между
inner classиnested-классом в Kotlin? - →Почему долгоживущий
Threadдействует как GC-корень для всего, что держит?
MiddleДебаггингРедкоКак два потока с чередующимися записями/чтениями могут напечатать [0, 0]?
Как два потока с чередующимися записями/чтениями могут напечатать [0, 0]?
Запись каждого потока может быть не видна другому, а переупорядочивание пускает оба чтения до закрепления записей, — поэтому [0, 0] легален. @Volatile на x и y добавляет рёбра happens-before, исключающие [0, 0], но [1, 1] требует явной синхронизации или join.
Типичные ошибки
- ✗Считать, что записи в порядке программы всегда мгновенно видны другим потокам
- ✗Думать, что
@Volatileгарантирует результат[1, 1], а не только исключает[0, 0] - ✗Полагать, что одиночным записям нужна атомарность, а не видимость/порядок
Уточняющие вопросы
- →Почему
@Volatileисключает[0, 0], но не форсирует[1, 1]? - →Как
joinперед чтениями гарантировал бы[1, 1]?