Я готовлюсь к экзамену Java SE 7 Programmer II. В одном из макетов экзаменов было упражнение, чтобы назвать, с какой проблемой потоковой передачи страдает код. Это код:
public class Test {
public static void main(String[] args) {
final Counter obj1 = new Counter("obj1");
final Counter obj2 = new Counter("obj2");
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("first");
obj1.display(obj2);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("second");
obj2.display(obj1);
}
}).start();
}
}
class Counter extends Thread {
int i = 10;
String name;
public Counter(String name) {
this.name = name;
}
public synchronized void display(Counter obj) {
try {
Thread.sleep(5);
obj.increment(this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void increment(Counter obj) {
System.out.println(++i);
}
}
Говорят, что это живое существо. Я не вижу этого. Пожалуйста, кто-нибудь может объяснить это более подробно.
Я бы не квалифицировал это как livelock на основе определения Википедии
Жилище похоже на тупик, за исключением того, что состояния процессов, вовлеченных в livelock, постоянно меняются друг относительно друга, ни одна из них не прогрессирует.
Хотя это соответствует определению тупика
В операционной системе тупик - это ситуация, когда процесс или поток входят в состояние ожидания, потому что запрашиваемый ресурс удерживается другим процессом ожидания, который, в свою очередь, ждет другого ресурса. Если процесс не может изменить свое состояние на неопределенное время, потому что запрашиваемые им ресурсы используются другим процессом ожидания, то система, как говорят, находится в тупике.
В первом потоке есть блокировка для obj1
, вторая имеет блокировку obj2
, затем запрашивает другую блокировку и блокировку.
После проверки состояния потоков я теперь уверен, что это тупик!
Я сохранил потоки в locals t1
и t2
, чтобы вызвать метод getState()
.
System.out.println(t*.getState())
сразу после запуска потоков, печатает: TIMED_WAITING.
То же самое в методе display
ПОСЛЕ Thread.sleep(5)
, печатает: RUNNABLE
И теперь ключевая часть:
Вызов System.out.println(t*.getState())
(для обоих потоков t1
и t2
) в main
снова, но на этот раз после sleep(5000)
будет напечатано BLOCKED.
Заблокированные средства: ожидание блокировки, а это означает, что это DEADLOCK!
Ran выше программы и получил дамп потока
для первой нити
"first@573" prio=5 tid=0xb nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
blocks second@575
waiting for second@575 to release lock on <0x245> (a dp.Counter)
at dp.Counter.increment(LiveLock.java:44)
at dp.Counter.display(LiveLock.java:37)
- locked <0x246> (a dp.Counter)
at dp.LiveLock.lambda$main$0(LiveLock.java:15)
at dp.LiveLock$$Lambda$1.16460856.run(Unknown Source:-1)
at java.lang.Thread.run(Thread.java:745)
и для второй нити
"second@575" prio=5 tid=0xc nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
blocks first@573
waiting for first@573 to release lock on <0x246> (a dp.Counter)
at dp.Counter.increment(LiveLock.java:44)
at dp.Counter.display(LiveLock.java:37)
- locked <0x245> (a dp.Counter)
at dp.LiveLock.lambda$main$1(LiveLock.java:20)
at dp.LiveLock$$Lambda$2.23661220.run(Unknown Source:-1)
at java.lang.Thread.run(Thread.java:745)
выглядит как тупиковая ситуация.
Вот как работает каждый поток:
run
потока, потому что Counter.display
synchronized
.display
.increment
другого объекта, поскольку метод increment
synchronized
. Помимо вопроса о том, что вы не устанавливаете i
volatile, потому что к нему можно получить доступ в многопоточном режиме, здесь есть ряд проблем, связанных с блокировкой, но главный из них:
Блокировка, удерживаемая на шаге 1
, не освобождается до того, как блокировка запрошена на этапе 3
. Для меня это потенциальный тупик, потому что оба потока будут заблокированы, если они оба пройдут этап 1
перед другими нажатиями 3
. Однако, поскольку возможно, что эти два потока действительно работают нормально, пока они никогда не попадают в это, тогда может быть возможно интерпретировать это как livelock, поскольку эти два потока могут с радостью работать вместе в течение некоторого времени, прежде чем они в конечном итоге станут смертоносными.
Поэтому я бы назвал этот потенциальный тупик и позволил педантам решить, как это назвать.