2016-08-05 2 views
4

Рассмотрим фрагмент взят из 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)?

+0

Есть ли причина, по которой вы не просто переместите присваивание 'num = 42' над регистрацией слушателя? Вот что пытается сказать вам «javaspecialist». – meriton

+0

@meriton: Спасибо, да, точно. Я прошу решения, кроме этого. Фактически, я пытаюсь сказать то же самое, что и в последней строке вопроса. –

+2

... и * почему * вы игнорируете лучшую практику? (Практика не была бы лучше, если бы существовали лучшие решения, не так ли?) – meriton

ответ

4

Как вы уже поняли, публикация this в конструкторе - очень плохая идея.

Это хороший способ обойти его; Используйте заводской метод.

public static ThisEscape newInstance(EventSource source){ 
    final ThisEscape instance = new ThisEscape(); 
    source.registerListener(new EventListener() { 

     @Override 
     public void onEvent(Event e) { 
      instance.doSomething(e); 
     } 
    } 
    return instance; 
} 
0

Это не имеет ничего общего с спасаясь this указатель. Тот факт, что num находится во внешнем классе, не меняет того факта, что параллельные действия необходимо синхронизировать.

Как отмечалось, есть 2 операции:

  1. registerListener
  2. Набор num = 42

Теперь onEvent обратного вызова может быть вызвана сразу после registerListener возвращения или даже до того, как вернется, потому что он является асинхронным. В любом случае либо до, либо послеnum = 42. Его необходимо синхронизировать или упорядочить должным образом.

1

При вызове registerListener() перед инициализацией поля num вы, очевидно, подвергаете себя риску доступа к номеру до его установки. Более того, к num обращается другой поток, поэтому нет гарантии, что после его установки будет считаться правильное значение.

Возможное решение будет intitialize num заранее

public static class ThisEscape { 
    private final int num = 42; 

    public ThisEscape(EventSource source) { 
     source.registerListener(e -> doSomething(e)); 
    } 
    //... 
} 

Или сделать его изменчивы и установить его перед registerListener() называется

public static class ThisEscape { 
    private volatile int num; 

    public ThisEscape(EventSource source) { 
     num = 42; 
     source.registerListener(e -> doSomething(e)); 
    } 
    //... 
} 

Edit: Спасибо @AndyTurner и @ShirgillFarhanAnsari для указания вне ошибки.

+0

Простое обращение вспять заявлений не гарантирует устранения проблемы. Вы получаете гарантию того, что присваивание произойдет до вызова 'doSomething', если вы зарегистрируете прослушиватель после завершения конструктора из-за того, что происходит окончательное назначение поля. –

+0

Первое решение в порядке. Второе не так, как я понял, что его не все гарантируют, что инструкция/операция, которая предшествует другой, гарантируется, что она будет выполнена до того же самого. –

Смежные вопросы