Ответ
Deadlock (взаимная блокировка) — это ситуация в многопоточном программировании, когда два или более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных друг другом, и не могут продолжить выполнение.
Условия возникновения deadlock'а (Коффмана):
- Взаимное исключение: Ресурс не может быть использован более чем одним потоком одновременно.
- Удержание и ожидание: Поток, удерживающий один ресурс, запрашивает другой.
- Отсутствие вытеснения: Ресурс может быть освобождён только добровольно удерживающим его потоком.
- Циклическое ожидание: Существует кольцевая цепочка потоков, каждый из которых ждёт ресурс, удерживаемый следующим в цепочке.
Классический пример deadlock'а:
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread A: Holding lock 1...");
try { Thread.sleep(10); } catch (InterruptedException e) {}
System.out.println("Thread A: Waiting for lock 2...");
synchronized (lock2) { // Блокируется здесь
System.out.println("Thread A: Acquired lock 2!");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lock2) { // Порядок захвата обратный!
System.out.println("Thread B: Holding lock 2...");
try { Thread.sleep(10); } catch (InterruptedException e) {}
System.out.println("Thread B: Waiting for lock 1...");
synchronized (lock1) { // Блокируется здесь
System.out.println("Thread B: Acquired lock 1!");
}
}
});
threadA.start();
threadB.start(); // Программа зависнет в deadlock'е.
}
}
Стратегии предотвращения deadlock'а:
- Упорядочивание блокировок: Всегда захватывать несколько блокировок в строго определённом глобальном порядке (например, сначала
lock1, потомlock2). Это нарушает условие циклического ожидания. - Использование
tryLockс таймаутом: Вjava.util.concurrent.locks.ReentrantLockиспользуйте методtryLock(long timeout, TimeUnit unit), который позволяет потоку отказаться от ожидания блокировки.if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) { try { if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) { try { // Критическая секция } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } - Избегание вложенных блокировок: Проектировать код так, чтобы потоку требовалась только одна блокировка за раз.
- Использование высокоуровневых примитивов: Замена ручной синхронизации на потокобезопасные коллекции из
java.util.concurrent(например,ConcurrentHashMap),ExecutorService,CountDownLatchи т.д.