Взаимоблокировка возникает, когда потоки (или что-то другое ваша платформа называет своими исполнительными модулями) получают ресурсы, при этом каждый ресурс может удерживаться только одним потоком за раз, и удерживает эти ресурсы таким образом, что удержания не могут быть вытеснены, и между потоками существует некоторая «круговая» взаимосвязь, так что каждый поток в тупике ожидает получения некоторого ресурса, удерживаемого другим потоком.
Таким образом, простой способ избежать взаимоблокировки - это упорядочить ресурсы и ввести правило, согласно которому ресурсы всегда будут получать только потоки по порядку . И наоборот, тупиковая ситуация может быть создана намеренно путем запуска потоков, которые получают ресурсы, но не получают их по порядку. Например:
Две нити, два замка. Первый поток запускает цикл, который пытается получить блокировки в определенном порядке, второй поток запускает цикл, который пытается получить блокировки в обратном порядке. Каждый поток снимает обе блокировки после успешного получения блокировок.
public class HighlyLikelyDeadlock {
static class Locker implements Runnable {
private Object first, second;
Locker(Object first, Object second) {
this.first = first;
this.second = second;
}
@Override
public void run() {
while (true) {
synchronized (first) {
synchronized (second) {
System.out.println(Thread.currentThread().getName());
}
}
}
}
}
public static void main(final String... args) {
Object lock1 = new Object(), lock2 = new Object();
new Thread(new Locker(lock1, lock2), "Thread 1").start();
new Thread(new Locker(lock2, lock1), "Thread 2").start();
}
}
В этом вопросе было несколько комментариев, указывающих на разницу между вероятностью и определенностью тупика. В некотором смысле это различие - академический вопрос. С практической точки зрения, я бы, конечно, хотел увидеть работающую систему, которая не блокировалась бы с кодом, который я написал выше :)
Однако вопросы собеседования иногда могут быть академическими, и в этом SO-вопросе действительно есть слово «конечно» в названии, так что далее следует программа, которая, безусловно, заходит в тупик. Создаются два Locker
объекта, каждому дается две блокировки и CountDownLatch
используется для синхронизации между потоками. Каждый Locker
замок блокирует первый замок, а затем один раз отсчитывает время фиксации. Когда оба потока получили блокировку и отсчитали фиксатор, они проходят мимо барьера защелки и пытаются получить вторую блокировку, но в каждом случае другой поток уже удерживает желаемую блокировку. Эта ситуация приводит к определенному тупику.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CertainDeadlock {
static class Locker implements Runnable {
private CountDownLatch latch;
private Lock first, second;
Locker(CountDownLatch latch, Lock first, Lock second) {
this.latch = latch;
this.first = first;
this.second = second;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
first.lock();
latch.countDown();
System.out.println(threadName + ": locked first lock");
latch.await();
System.out.println(threadName + ": attempting to lock second lock");
second.lock();
System.out.println(threadName + ": never reached");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(final String... args) {
CountDownLatch latch = new CountDownLatch(2);
Lock lock1 = new ReentrantLock(), lock2 = new ReentrantLock();
new Thread(new Locker(latch, lock1, lock2), "Thread 1").start();
new Thread(new Locker(latch, lock2, lock1), "Thread 2").start();
}
}