Рассмотрим фрагмент взят из here:Как избежать состояния гонки, обнаруженного в следующем коде?
// event
public class Event { }
// An Event Listener
public interface EventListener {
public void onEvent(Event e);
}
// inner class instances contain a hidden reference to the enclosing instance
public class ThisEscape {
private final int num;
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
@Override
public void onEvent(Event e) {
doSomething(e);
}
});
num = 42;
}
private void doSomething(Event e) {
if (num != 42) {
System.out.println("Race condition detected at " + new Date());
}
}
}
// event source
public class EventSource extends Thread {
private final BlockingQueue<EventListener> listeners =
new LinkedBlockingQueue<EventListener>();
public void run() {
while (true) {
try {
listeners.take().onEvent(null);
} catch (InterruptedException e) {
break;
}
}
}
public void registerListener(EventListener eventListener) {
listeners.add(eventListener);
}
}
// testing the conditions
public class ThisEscapeTest {
public static void main(String[] args) {
EventSource es = new EventSource();
es.start();
while (true) {
new ThisEscape(es);
}
}
}
Для консолидации, у нас есть 2 темы
// Main Thread
// Event Source
В потоке Источник события, есть BlockingQueue для хранения EventListener. В способе выполнения по той же теме,
потребляя EventSource нить продолжает принимать объекты из очереди блокировки и обрабатывает их. Если тот же поток пытается вывести объект из пустой очереди, тот самый поток блокируется до тех пор, пока producing thread
(Main Thread) помещает объект в очередь.
Поскольку эти 2 операции (ниже) не являются атомарными, из-за какой-то несчастливый timimg, поэтому между теми же 2 операции, то весьма вероятно, что EventSource может обнаружить, что Num! = 2 & поэтому состояние гонки.
source.registerListener(new EventListener() { // OPERATION 1
@Override
public void onEvent(Event e) {
doSomething(e);
}
});
num = 42; // OPERATION 2
}
Поскольку, как было предложено и ясно видно, внутренние экземпляры классов содержат скрытую ссылку на прилагаемый экземпляр.
Хотя замок был приобретен в том же потоке (Главная тема), не-синхронизированный метод doSomething()
все еще могут быть доступны другим потоком (в данном случае EventSource) в то же время, я вижу, что даже синхронизация 2 операции выше не будут препятствовать условиям гонки. Правильно ли я понимаю? Я имею в виду
public ThisEscape(EventSource source) {
synchronized(this){ // SYNCHRONISED
source.registerListener(new EventListener() {
@Override
public void onEvent(Event e) {
doSomething(e);
}
});
num = 42;
}
}
И единственный способ избежать состояния гонки является сделать doSomething()
метод также синхронизированный, помимо синхронизации 2 операции?
В-третьих, я вижу, является ли поле окончательным или нет, это не имеет никакого значения. Состояние гонки все равно будет. Что именно представляет собой авторский вопрос о финале (кроме создания private final int num = 42
)?
Есть ли причина, по которой вы не просто переместите присваивание 'num = 42' над регистрацией слушателя? Вот что пытается сказать вам «javaspecialist». – meriton
@meriton: Спасибо, да, точно. Я прошу решения, кроме этого. Фактически, я пытаюсь сказать то же самое, что и в последней строке вопроса. –
... и * почему * вы игнорируете лучшую практику? (Практика не была бы лучше, если бы существовали лучшие решения, не так ли?) – meriton