В классическом примере взаимоблокировки есть два пути в коде, использующие одни и те же две синхронизированные блокировки, но в другом порядке, как здесь:
// Production code
public class Deadlock {
final private Object monitor1;
final private Object monitor2;
public Deadlock(Object monitor1, Object monitor2) {
this.monitor1 = monitor1;
this.monitor2 = monitor2;
}
public void method1() {
synchronized (monitor1) {
tryToSleep(1000);
synchronized (monitor2) {
tryToSleep(1000);
}
}
}
public void method2() {
synchronized (monitor2) {
tryToSleep(1000);
synchronized (monitor1) {
tryToSleep(1000);
}
}
}
public static void tryToSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Это потенциально может привести к взаимоблокировке. Чтобы увеличить вероятность того, что он действительно зайдет в тупик, я добавляю эти tryToSleep(1000);
, просто чтобы гарантировать, что method1 получит блокировку на monitor1, а method2 получит блокировку на monitor2, прежде чем даже пытаться получить следующую блокировку. Таким образом, используя сон, этот тупик имитирует «неудачное» время. Скажем, есть странное требование, чтобы наш код мог привести к тупиковой ситуации, и по этой причине мы хотим его протестировать:
// Test
@Test
void callingBothMethodsWillDeadlock() {
var deadlock = new Deadlock(Integer.class, String.class);
var t1 = new Thread(() -> {
deadlock.method1(); // Executes for at least 1000ms
});
t1.start();
var t2 = new Thread(() -> {
deadlock.method2(); // Executes for at least 1000ms
});
t2.start();
Deadlock.tryToSleep(5000); // We need to wait for 2s + 2s + some more to be sure...
assertEquals(Thread.State.BLOCKED, t1.getState());
assertTrue(t1.isAlive());
assertEquals(Thread.State.BLOCKED, t2.getState());
assertTrue(t2.isAlive());
}
Успешно, и это хорошо. Что плохо, так это то, что мне пришлось добавить сон в сам класс Deadlock, а также в его тест. Пришлось сделать это только для того, чтобы тест стабильно проходил. Несмотря на то, что если я убираю сон отовсюду, этот код может иногда приводить к взаимоблокировке, но тогда нет гарантии, что это произойдет во время теста. Теперь предположим, что сон здесь неприемлем, тогда возникает вопрос: как я могу надежно проверить, что этот код может вызвать тупик без сна ни в тесте, ни в самом коде?
edit : Я просто хотел подчеркнуть, что я прошу класс иметь возможность зайти в тупик, только в некоторый «неудачный» момент (когда два потока вызывают method1()
и method2()
одновременно), этот тупик должен произойти . И в своем тесте я хочу продемонстрировать тупик при каждом запуске. Я хочу удалить вызовы сна из производственного кода (надеюсь, из теста). Может быть, есть способ использовать моки вместо внедренных мониторов, чтобы мы могли организовать их получение блокировок в определенном c порядке во время теста?