Плюсы и минусы слушателей как слабые ссылки - PullRequest
70 голосов
/ 14 июня 2011

Каковы плюсы и минусы сохранения слушателей в качестве WeakReferences.

Большое «Pro», конечно, заключается в том, что:

Добавление слушателя в качестве WeakReference означает, что слушателю не нужно беспокоиться«удаление» само по себе.

Обновление

Для тех, кто беспокоится о том, что у слушателя есть единственная ссылка на объект, почему не может быть 2 метода, addListener () и addWeakRefListener()?

те, кто не заботится об удалении, могут использовать последнее.

Ответы [ 12 ]

69 голосов
/ 23 июня 2011

Прежде всего, использование WeakReference в списках слушателей придаст вашему объекту различную семантику , а затем использование жестких ссылок.В случае жесткой ссылки addListener (...) означает «уведомить предоставленный объект о конкретном событии (ях) , пока я не остановлю его явно с помощью removeListener (..)», в случае слабой ссылки это означает «уведомить»предоставленный объект о конкретном событии (ях) до тех пор, пока этот объект не будет использоваться кем-либо еще (или явно остановлен с помощью removeListener) ".Обратите внимание, что во многих ситуациях совершенно законно иметь объект, прослушивать некоторые события и не иметь других ссылок, хранящих его в GC.Логгер может быть примером.

Как видите, использование WeakReference не только решает одну проблему («я должен иметь в виду, чтобы не забыть удалить добавленного слушателя где-то»), но и поднимает другую - «Я должен помнить, что мойслушатель может прекратить слушать в любой момент, когда на него больше нет ссылок ».Вы не решаете проблему, вы просто меняете одну проблему на другую.Посмотрите, любым способом вы вынуждены четко определить, спроектировать и отследить livespan вашего слушателя - так или иначе.

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

PS Также следует отметить, что WeakReference вводит дополнительный уровень косвенности, который, в некоторых случаях при чрезвычайно высокой частоте событий, может снизить производительность.

31 голосов
/ 14 июня 2011

Это не полный ответ, но сама сила, которую вы цитируете, также может быть ее главной слабостью.Подумайте, что произойдет, если слушатели действий будут реализованы слабо:

button.addActionListener(new ActionListener() {
    // blah
});

Этот слушатель действий будет собирать мусор в любой момент!Нередко единственная ссылка на анонимный класс - это событие, к которому вы добавляете его.

10 голосов
/ 19 июня 2011

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

Если только один класс полагается на слушателя, то его легко очистить, но что произойдет, если 25 классов полагаются на него?Становится намного сложнее отменить их регистрацию должным образом.Дело в том, что ваш код может начинаться с одного объекта, ссылающегося на ваш слушатель, и заканчиваться в следующей версии 25 объектами, ссылающимися на тот же самый слушатель.

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

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

Я ненавижу код, создающий экземпляры анонимных классов слушателей (как упомянуто Кирком Воллом), потому что после регистрации вы не можете больше не регистрировать этих слушателей.У вас нет ссылки на них.ИМХО это действительно плохое кодирование.

Вы также можете null ссылаться на слушателя, когда он вам больше не нужен.Вам больше не нужно об этом беспокоиться.

5 голосов
/ 14 июня 2011

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

Обновление:

Хорошо, я думаю, я мог понять, к чему вы клоните.Если вы добавляете недолговечные прослушиватели к долгоживущим объектам, может быть полезно использовать слабую ссылку.Так, например, если вы добавляете PropertyChangeListeners в свои доменные объекты для обновления состояния графического интерфейса, который постоянно воссоздается, доменные объекты будут держаться за графический интерфейс, который может нарастать.Подумайте о большом всплывающем диалоге, который постоянно воссоздается, со ссылкой слушателя на объект Employee через PropertyChangeListener.Поправьте меня, если я ошибаюсь, но я не думаю, что весь шаблон PropertyChangeListener больше популярен.

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

Вот пара интересных прочтений:

http://www.javalobby.org/java/forums/t19468.html

Как устранить утечки памяти в обработчике колебания?

4 голосов
/ 20 июня 2011

Если честно, я на самом деле не покупаю эту идею и именно то, что вы ожидаете сделать с addWeakListener. Может быть, это только я, но, похоже, это неправильная хорошая идея. Сначала это соблазняет, но проблемы, которые это может подразумевать, не пренебрежимо малы.

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

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

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

То, чего вы пытаетесь достичь, опасно, так как вы больше не можете контролировать, когда прекращаете получать уведомления. Они могут длиться вечно или останавливаться слишком рано.

3 голосов
/ 22 июня 2011

Поскольку вы добавляете прослушиватель WeakReference, я полагаю, вы используете настраиваемый объект Observable.

Имеет смысл использовать WeakReference для объекта в следующей ситуации.- Есть список слушателей в наблюдаемом объекте.- У вас уже есть жесткая ссылка на слушателей где-то еще.(вы должны быть уверены в этом) - Вы не хотите, чтобы сборщик мусора прекратил очистку слушателей только потому, что в Observable есть ссылка на это.- Во время сбора мусора слушатели будут очищены.В методе, где вы уведомляете слушателей, вы очищаете объекты WeakReference из списка уведомлений.

2 голосов
/ 21 марта 2015

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

На практике я вижу много программ, которые навсегда удерживают слушателей.Часто программисты даже не знают, что им следует отменить их регистрацию.

Обычно можно вернуть пользовательский объект со ссылкой на слушателя, что позволяет манипулировать моментом отмены регистрации.Например:

listeners.on("change", new Runnable() {
  public void run() {
    System.out.println("hello!");
  }
}).keepFor(someInstance).keepFor(otherInstance);

этот код регистрирует прослушиватель, возвращает объект, который инкапсулирует прослушиватель и имеет метод keepFor, который добавляет прослушиватель в статический weakHashMap с параметром экземпляра в качестве ключа.Это гарантировало бы, что прослушиватель зарегистрирован, по крайней мере, до тех пор, пока someInstance и otherInstance не будут собирать мусор.

Могут быть другие методы, такие как keepForever () или keepUntilCalled (5) или keepUntil (DateTime.now ().plusSeconds (5)) или unregisterNow ().

Значение по умолчанию можно сохранить навсегда (пока не будет зарегистрировано).

Это также может быть реализовано без слабых ссылок, но с фантомными ссылками, которые инициируют удаление слушателя.

edit: создана небольшая библиотека, которая реализует базовую версию этого подхода https://github.com/creichlin/struwwel

2 голосов
/ 14 июня 2011

Я не могу придумать ни одного законного варианта использования для использования WeakReferences для слушателей, если только каким-то образом в вашем сценарии использования не используются прослушиватели, которые явно не должны существовать после следующего цикла GC (этот вариант использования, конечно, будет VM / platform конкретные).

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

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

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

1 голос
/ 09 апреля 2015

У меня есть 3 предложения для оригинального постера. Извините за возрождение старой темы, но я думаю, что мои решения ранее не обсуждались в этой теме.

Во-первых, Рассмотрим следующий пример javafx.beans.values.WeakChangeListener в библиотеках JavaFX.

Во-вторых, Я поднял шаблон JavaFX, изменив методы addListener моего Observable. Новый метод addListener () теперь создает экземпляры соответствующих классов WeakXxxListener для меня.

Метод «событие пожара» был легко изменен, чтобы разыменовать XxxWeakListeners и удалить их, когда WeakReference.get () вернул ноль.

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

В-третьих, До реализации этой стратегии я использовал другой метод, который может оказаться полезным. Слушатели (с жесткой ссылкой) получили новое событие, которое они проверили на предмет того, используются ли они до сих пор. Если нет, то они отписались от наблюдателя, что позволило им быть GCed. Для недолговечных слушателей, подписавшихся на долгоживущие Observables, обнаружить устаревание было довольно легко.

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

0 голосов
/ 02 июля 2014

Из тестовой программы видно, что анонимные ActionListeners не будут препятствовать сборке мусора объектом:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;

public class ListenerGC {

private static ActionListener al = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.err.println("blah blah");
        }
    };
public static void main(String[] args) throws InterruptedException {

    {
        NoisyButton sec = new NoisyButton("second");
        sec.addActionListener(al);
        new NoisyButton("first");
        //sec.removeActionListener(al);
        sec = null;
    }
    System.out.println("start collect");
    System.gc( );
    System.out.println("end collect");
    Thread.sleep(1000);
    System.out.println("end program");
}

private static class NoisyButton extends JButton {
    private static final long serialVersionUID = 1L;
    private final String name;

    public NoisyButton(String name) {
        super();
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println(name + " finalized");
        super.finalize();
    }
}
}

производит:

start collect
end collect
first finalized
second finalized
end program
...