Почему ни один поток не закончит, когда два лока берутся в обратном порядке?
Два потока берут cup и coffeeMaker, но в обратном порядке. Объясните, почему программа может зависнуть и как это исправить.
// поток 1
synchronized(cup) {
synchronized(coffeeMaker) { /* ... */ }
}
// поток 2
synchronized(coffeeMaker) {
synchronized(cup) { /* ... */ }
}
Определите причину и исправьте.
Поток 1 держит cup и ждёт coffeeMaker; поток 2 держит coffeeMaker и ждёт cup. Каждый ждёт лок, который держит другой, — это deadlock, никто не продвигается. Решение — брать оба лока в едином глобальном порядке во всех потоках.
- ✗Считать, что реентерабельность
synchronizedспасает от deadlock на двух объектах - ✗Думать, что планировщик ОС сам со временем разорвёт deadlock
- ✗Полагать, что разный порядок локов в потоках безвреден
- →Какие четыре условия должны выполняться одновременно для возникновения deadlock?
- →Как
tryLockс таймаутом мог бы избежать зависания вместо фиксированного порядка?
Баг
Классический deadlock из-за несогласованного порядка захвата локов:
// поток 1
synchronized(cup) { // держит cup
synchronized(coffeeMaker) { /* ... */ } // ждёт coffeeMaker
}
// поток 2
synchronized(coffeeMaker) { // держит coffeeMaker
synchronized(cup) { /* ... */ } // ждёт cup
}
Поток 1 держит cup и ждёт coffeeMaker; поток 2 держит coffeeMaker и ждёт cup. Возникает круговое ожидание — оба зависают навсегда.
Исправление
Захватывайте локи в едином глобальном порядке во всех потоках — например, всегда сначала cup, потом coffeeMaker:
// оба потока
synchronized(cup) {
synchronized(coffeeMaker) { /* ... */ }
}
Единый порядок разрывает условие кругового ожидания, и deadlock становится невозможен.