Этот вопрос мне задали года полтора назад на первом телефонном собеседовании в Майкрософт. Я ответил что-то невнятное типа «ну концентрировать весь concurrency-related код в как можно меньшем числе классов, чтобы удобнее было разбираться в проблеме». Это собеседование я не прошёл.
Java Concurrency in Practice для решения проблемы дедлоков предлагает, по крайней мере в главе про дедлоки, следующее решение:
If you must acquire multiple locks, lock ordering must be a part of your design: try to minimize the number of potential locking interactions, and follow and document a lock ordering protocol for locks that may be acquired together.
Однако более полный ответ на этот вопрос я увидел, как ни странно, в Чистом коде Боба Мартина, в той главе, которая там вроде как ни к месту. Там приводятся следующие обязательные условия возникновения дедлока:
- Mutual Exclusion
- Lock and Wait
- No Preemption
- Circular Wait
И предлагаются способы исключить каждое условие:
Mutual Exclusion:
- Использовать ресурсы, не требующие блокировки
- Увеличение количества ресурсов, чтобы у каждого потока был свой экземпляр
- Проверка, что ресурс свободен перед тем, как захватить его
От Lock and Wait избавиться можно так: проверяем ресурс перед захватом; если он занят, то освобождаем все захваченные ресурсы. Этот подход может привести к новым проблемам: Livelock и Starvation.
No Preemption: разрешаем потокам запрашивать ресурсы друг у друга. Я никогда в жизни такого не видел и не представляю, как это хорошо реализовать. Пишут, что это непросто.
Circular Wait: Вводим порядок, в котором ресурсы могут быть захвачены. Это невозможно, если до захвата одного ресурса мы не узнаем, какой ресурс понадобится следующим.
* * *
Особые юмористы могут отметить при ответе на вопрос, что переход на один лок или один поток также решает проблему.