2016-04-23 7 views
2

Я читаю книгу Java concurrency in practice, в разделе 3.2, он дает следующий пример кода для иллюстрации неявно позволяя this ссылки бежать (Не делайте этого, Especailly в конструкторе):создать экземпляр внутреннего класса в конструкторе

public class ThisEscape { 
    public ThisEscape(EventSource source) { 
     source.registerListener ( 
      new EventListener() { 
       public void onEvent(Event e) { 
        doSomething(e); 
       } 
      } 
     ); 
    } 
} 

книга затем говорит:

Когда ThisEscape публикует EventListener, неявно публикует охватывающую ThisEscape экземпляр, а также, потому что внутренняя экземпляры классов содержат скрытую ссылку на экземпляр-экземпляр.

Я понимаю вышеупомянутые слова с точки зрения Java, но я не могу придумать пример того, как могут EventListener избежать ограждающих ссылок приведенного выше кода в this быть вредными? В каком смысле?

Например, если я создаю новый экземпляр ThisEscape:

ThisEscape myEscape = new Escape(mySource); 

Тогда что? Как это вредно сейчас? В каком смысле это вредно?

Может ли кто-нибудь использовать код выше в качестве базы и объяснить мне, как это вредно?

======= MORE ======

Книга пытается сказать что-то вроде анонимного EventListener имеет скрытую ссылку на вмещающем экземпляр класса, который еще не полностью построенный. Я хочу знать, например, как можно неправильно использовать эту не полностью сконструированную ссылку, и я предпочитаю видеть пример кода по этому вопросу.

Книга дает правильный способ делать вещи, это использовать статический фабричный метод, как показано ниже:

public static SafeListener newInstance(EventSource source) { 
     SafeListener safe = new SafeListener(); 
     source.registerListener (safe.listener); 
     return safe; 
} 

Я просто не получить точку целиком.

+0

Спасибо, что задали этот вопрос. – Tirath

ответ

3

Задача 1: Работая на не-полностью сконструированный объект

Рассмотрим слегка измененный пример:

public class ThisEscape { 
    private String prefixText = null; 

    private void doSomething(Event e) { 
     System.out.println(prefixText.toUpperCase() + e.toString()); 
    } 

    public ThisEscape(EventSource source) { 
     source.registerListener( 
      new EventListener() { 
       public void onEvent(Event e) { 
        doSomething(e); // hidden reference to `ThisEscape` is used 
       } 
      } 
     ); 

     // What if an event is fired at this point from another thread? 
     // prefixText is not yet assigned, 
     // and doSomething() relies on it being not-null 

     prefixText = "Received event: "; 
    } 
} 

Это введет тонкий и очень трудно, чтобы найти ошибку, например, в многопоточных Приложения.

Считает, что источник события пожары и событие после source.registerListener(...) завершили, но перед тем prefixText был назначен. Это может произойти в другой теме.

В этом случае doSomething() получит доступ к неинициализированному полю prefixText, что приведет к NullPointerException. В других сценариях результатом может быть неправильное поведение или неправильные результаты расчета, что будет хуже, чем исключение.И такая ошибка чрезвычайно сложно найти в реальных приложениях, в основном из-за того, что it happens sporadically.

Задача 2: Сбор мусора

Скрытая ссылка на ограждающий экземпляр будет препятствовать сборщик мусора от очистки вверх «охватывающей экземпляр» в некоторых случаях.

Это произойдет, если экземпляр окружения больше не нужен логикой программы, но экземпляр внутреннего класса, который он создал, по-прежнему необходим.

Если «охватывающий экземпляр» в свою очередь содержит ссылки на множество других объектов, которые не нужны логике программы, это приведет к массивной утечке памяти .


Пример кода. Учитывая слегка измененный ThisEscape класс образует вопрос:

public class ThisEscape { 

    private long[] aVeryBigArray = new long[4711 * 815]; 

    public ThisEscape(EventSource source) { 
     source.registerListener( 
      new EventListener() { 
       public void onEvent(Event e) { 
        doSomething(e); 
       } 
       private void doSomething(Event e) { 
        System.out.println(e.toString()); 
       } 
      } 
     ); 
    } 
} 

Пожалуйста, обратите внимание, что внутренний анонимный класс (который простирается/орудие EventListener) не является статическим и, таким образом, содержит скрытую ссылку на экземпляр содержащего класса (ThisEscape).

Также обратите внимание, что анонимный класс фактически не использует эту скрытую ссылку: в классе аноним не используются нестатические методы или поля из содержащего класса.

Теперь это может быть возможным использование:

// Register an event listener to print the event to System.out 
new ThisEscape(myEventSource); 

С помощью этого кода мы хотели добиться того, что событие регистрируется в myEventSource. Нам больше не нужен экземпляр ThisEscape.

Но если предположить, что метод EventSource.registerListener(EventListener) сохраняет ссылку на слушатель событий, созданный в ThisEscape, и анонимный слушатель событий имеет скрытую ссылку на экземпляр класса, содержащем, экземпляра ThisEscape не может быть сборщиком мусора.

Я намеренно поставить большой нестатический long массив в ThisEscape, чтобы продемонстрировать, что экземпляр класса ThisEscape может провести на самом деле много данных (прямо или косвенно), так что утечка памяти может быть значительной.

+0

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

+0

Пожалуйста, проверьте мои обновления в моем сообщении. Благодарю. –

+0

Спасибо за обновленный вопрос; Включен сценарий использования не полностью инициализированного объекта. –

2

Проблема с публикацией объекта mid-construction в многопоточном контексте заключается в том, что объект может использоваться до завершения строительства (или после того, как конструктор создал исключение).

Даже если издательство происходит как последний явный шаг в конструкторе, есть три вещи, которые нужно иметь в виду:

  • Порядок побочных эффектов в потоке делает не определяют порядок которые эти побочные эффекты становятся видимыми для других потоков.Поэтому даже если конструктор написан таким образом, что он полностью заполняет объект, прежде чем он публикует ссылку на него, нет никакой гарантии, что другие потоки будут видеть полностью заполненный объект при чтении ссылки.

  • Поле final обычно имеет специальные свойства параллелизма, но эти свойства зависят от достижения конца конструктора, прежде чем объект станет видимым для других потоков. Если другие потоки воспринимают объект до его полной сборки, тогда они могут даже не увидеть правильные значения полей final.

  • Конструкторы суперкласса вызываются до того, как любая инициализация произойдет в подклассе. Так, например, если подкласс содержит поле String foo = "foo", то во время конструктора суперкласса поле все равно будет null, что повлияет на результаты использования виртуальных методов. Поэтому, если ссылка на объект публикуется во время конструктора суперкласса, другие потоки могут воздействовать на объект, пока он находится в неполном (и причудливом) состоянии.

+0

Спасибо, это очень хорошее объяснение! –

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