Напиши код для гарантированного дедлока
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Гарантированный дедлок в Java
Дедлок — это состояние, когда два или больше потока ждут друг друга в круговой зависимости, и ни один из них не может продолжить работу. Ниже я покажу классический случай и несколько вариаций.
Классический дедлок: два потока, два lock-а
import java.util.concurrent.CountDownLatch;
public class DeadlockExample {
static class Account {
private int balance;
private int id;
public Account(int id, int balance) {
this.id = id;
this.balance = balance;
}
public synchronized void transfer(Account to, int amount) {
System.out.println(Thread.currentThread().getName() +
" пытается перевести " + amount + " со счёта " + this.id +
" на счёт " + to.id);
// Это создаст дедлок!
synchronized (to) {
System.out.println(Thread.currentThread().getName() +
" получил оба лока");
this.balance -= amount;
to.balance += amount;
System.out.println(Thread.currentThread().getName() +
" завершил перевод");
}
}
}
public static void main(String[] args) throws InterruptedException {
Account account1 = new Account(1, 1000);
Account account2 = new Account(2, 2000);
// Лок 1: поток 1 получит account1, потом будет ждать account2
// Лок 2: поток 2 получит account2, потом будет ждать account1
// Результат: ДЕДЛОК
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
account1.transfer(account2, 100);
}
}, "Thread-1");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
account2.transfer(account1, 100);
}
}, "Thread-2");
thread1.start();
thread2.start();
// Ждём 5 секунд — потоки будут зависшими
Thread.sleep(5000);
System.out.println("\n⚠️ ДЕДЛОК! Потоки зависли.");
}
}
Почему это дедлок?
Этап 1:
Thread-1: synchronized (account1) — получил лок 1 ✓
Thread-2: synchronized (account2) — получил лок 2 ✓
Этап 2:
Thread-1: synchronized (account2) — ЖДЁТ лок 2 (занят Thread-2) ⏳
Thread-2: synchronized (account1) — ЖДЁТ лок 1 (занят Thread-1) ⏳
Вечный цикл ожидания — ДЕДЛОК!
Условия возникновения дедлока
Необходимы ВСЕ 4 условия одновременно:
- Взаимное исключение — ресурс может быть занят только одним потоком
- Держание и ожидание — поток держит ресурс и ждёт другого
- Невозможность отнятия — нельзя отобрать ресурс у потока
- Круговое ожидание — цепочка потоков ждёт друг друга кругом
Вариант 2: дедлок с ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDeadlock {
static class Resource {
private ReentrantLock lock = new ReentrantLock();
private String name;
public Resource(String name) {
this.name = name;
}
public void access(Resource other) {
lock.lock();
System.out.println(Thread.currentThread().getName() +
" захватил лок " + this.name);
try {
Thread.sleep(100); // Симулируем обработку
other.lock.lock();
System.out.println(Thread.currentThread().getName() +
" захватил лок " + other.name);
try {
System.out.println("Успешно!");
} finally {
other.lock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Resource resource1 = new Resource("Resource-1");
Resource resource2 = new Resource("Resource-2");
Thread thread1 = new Thread(() -> {
resource1.access(resource2);
}, "T1");
Thread thread2 = new Thread(() -> {
resource2.access(resource1);
}, "T2");
thread1.start();
thread2.start();
Thread.sleep(3000);
System.out.println("⚠️ Дедлок с ReentrantLock!");
}
}
Вариант 3: дедлок с несколькими потоками
public class MultiThreadDeadlock {
static Object lock1 = new Object();
static Object lock2 = new Object();
static Object lock3 = new Object();
public static void main(String[] args) {
// Thread-1: захватит lock1, ждёт lock2
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("T1 захватил lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("T1 захватил lock2");
}
}
});
// Thread-2: захватит lock2, ждёт lock3
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("T2 захватил lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock3) {
System.out.println("T2 захватил lock3");
}
}
});
// Thread-3: захватит lock3, ждёт lock1
Thread t3 = new Thread(() -> {
synchronized (lock3) {
System.out.println("T3 захватил lock3");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("T3 захватил lock1");
}
}
});
t1.start();
t2.start();
t3.start();
// Все три потока зависнут в круговом ожидании
}
}
Вариант 4: дедлок с BlockingQueue
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueDeadlock {
static class Producer implements Runnable {
private BlockingQueue<String> queue1;
private BlockingQueue<String> queue2;
public Producer(BlockingQueue<String> queue1, BlockingQueue<String> queue2) {
this.queue1 = queue1;
this.queue2 = queue2;
}
@Override
public void run() {
try {
String item = queue1.take(); // Ждёт элемента из queue1
System.out.println("P: получил " + item);
queue2.put(item + "-processed"); // Кладёт в queue2
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue1 = new LinkedBlockingQueue<>(1);
BlockingQueue<String> queue2 = new LinkedBlockingQueue<>(1);
// Заполняем обе очереди
queue1.put("item1");
queue2.put("item2");
// Теперь очереди полные
// Thread1: попробует взять из queue1 и положить в queue2
// Но queue2 полная! Ждёт, пока из queue2 возьмут
Thread t1 = new Thread(new Producer(queue1, queue2), "T1");
// Thread2: попробует взять из queue2 и положить в queue1
// Но queue1 полная! Ждёт, пока из queue1 возьмут
Thread t2 = new Thread(new Producer(queue2, queue1), "T2");
t1.start();
t2.start();
Thread.sleep(2000);
System.out.println("⚠️ Дедлок с BlockingQueues!");
}
}
Диагностика дедлока: Thread Dump
Если программа зависла, можно получить информацию о дедлоке:
# Найти PID процесса Java
jps
# Получить thread dump
jstack <PID>
# Вывод покажет:
# Found one Java-level deadlock:
# =============================
# "Thread-2":
# waiting to lock monitor 0x00007f8b8d...
# (owned by "Thread-1" at 0x00007f8b8d...)
Как избежать дедлока?
1. Всегда захватывать локи в одинаковом порядке
// ❌ Плохо — может быть дедлок
thread1: synchronized(A) { synchronized(B) {} }
thread2: synchronized(B) { synchronized(A) {} }
// ✅ Хорошо — обе потоки берут в порядке A → B
thread1: synchronized(A) { synchronized(B) {} }
thread2: synchronized(A) { synchronized(B) {} }
2. Использовать timeout
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// Работаем с ресурсом
} finally {
lock.unlock();
}
} else {
System.out.println("Не смогли получить лок за 5 секунд");
}
3. Использовать высокоуровневые инструменты
import java.util.concurrent.ConcurrentHashMap;
// Вместо synchronized — используем ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
4. Deadlock detection в production
public class DeadlockDetector extends Thread {
@Override
public void run() {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
while (true) {
long[] deadlockedThreads = bean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
System.err.println("ДЕДЛОК ОБНАРУЖЕН!");
ThreadInfo[] info = bean.getThreadInfo(deadlockedThreads);
for (ThreadInfo ti : info) {
System.err.println(ti);
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
break;
}
}
}
}
Заключение
Дедлок — это серьёзная проблема в многопоточных приложениях. Он возникает, когда два или более потока ждут друг друга в круговой зависимости. Главное правило для избежания дедлока — всегда захватывать локи в одинаковом порядке. Используйте высокоуровневые инструменты (ConcurrentHashMap, CountDownLatch, Semaphore), избегайте вложенной синхронизации, и применяйте timeout при захвате локов.