Was sind die Vor- und Nachteile, wenn Hörer als schwache Referenzen gehalten werden?
Der große "Profi" ist natürlich:
Das Hinzufügen eines Listeners als WeakReference bedeutet, dass sich der Listener nicht die Mühe machen muss, sich selbst zu entfernen.
Aktualisieren
Für diejenigen, die sich Sorgen machen, dass der Listener den einzigen Verweis auf das Objekt hat, warum kann es nicht zwei Methoden geben, addListener () und addWeakRefListener ()?
diejenigen, die sich nicht für die Entfernung interessieren, können letztere verwenden.
Antworten:
Wenn Sie WeakReference in Listener-Listen verwenden, erhält Ihr Objekt zunächst eine andere Semantik , und dann werden harte Referenzen verwendet. Im Fall einer harten Referenz bedeutet addListener (...) "das gelieferte Objekt über bestimmte Ereignisse benachrichtigen, bis ich es explizit mit removeListener (..) stoppe", im Fall einer schwachen Referenz bedeutet dies "das gelieferte Objekt über ein bestimmtes Ereignis benachrichtigen ("). s) bis dieses Objekt von niemand anderem verwendet wird (oder explizit mit removeListener beendet wird) ". Beachten Sie, dass es in vielen Situationen vollkommen legal ist, ein Objekt zu haben, auf bestimmte Ereignisse zu warten und keine anderen Referenzen zu haben, die es von GC abhalten. Logger kann ein Beispiel sein.
Wie Sie sehen können, löst die Verwendung von WeakReference nicht nur ein Problem ("Ich sollte bedenken, dass ich nicht vergesse, irgendwo hinzugefügte Listener zu entfernen"), sondern auch ein anderes - "Ich sollte bedenken, dass mein Listener bei jedem aufhören kann, zuzuhören Moment, in dem es keinen Hinweis mehr gibt ". Sie lösen kein Problem, Sie tauschen nur ein Problem gegen ein anderes. Schauen Sie, in irgendeiner Weise haben Sie gezwungen, die Lebensspanne Ihres Zuhörers klar zu definieren, zu gestalten und zu verfolgen - auf die eine oder andere Weise.
Ich persönlich stimme der Erwähnung zu, dass die Verwendung von WeakReference in Listener-Listen eher ein Hack als eine Lösung ist. Es ist ein Muster, über das es sich zu wissen lohnt, manchmal kann es Ihnen helfen - zum Beispiel, damit Legacy-Code gut funktioniert. Aber es ist kein Muster der Wahl :)
PS Es sollte auch beachtet werden, welche WeakReference eine zusätzliche Indirektionsebene einführt, die in einigen Fällen mit extrem hohen Ereignisraten die Leistung verringern kann.
quelle
addWeakListener(listener)
, eine gute Vorgehensweise, da Benutzer dieser Klasse eine Vorstellung davon haben, was im Inneren vor sich geht, um zu vermeiden, dass Benachrichtigungen fehlenDies ist keine vollständige Antwort, aber die Stärke, die Sie zitieren, kann auch die Hauptschwäche sein. Überlegen Sie, was passieren würde, wenn Action Listener schwach implementiert würden:
button.addActionListener(new ActionListener() { // blah });
Dieser Action-Listener wird jeden Moment Müll sammeln lassen! Es ist nicht ungewöhnlich, dass der einzige Verweis auf eine anonyme Klasse das Ereignis ist, zu dem Sie sie hinzufügen.
quelle
Ich habe Tonnen von Code gesehen, bei denen die Hörer nicht richtig abgemeldet waren. Dies bedeutet, dass sie immer noch unnötig aufgerufen wurden, um unnötige Aufgaben auszuführen.
Wenn sich nur eine Klasse auf einen Hörer verlässt, ist es leicht zu reinigen, aber was passiert, wenn 25 Klassen sich darauf verlassen? Es wird viel schwieriger, die Registrierung ordnungsgemäß aufzuheben. Tatsache ist, dass Ihr Code mit einem Objekt beginnen kann, das auf Ihren Listener verweist, und in einer zukünftigen Version mit 25 Objekten enden kann, die auf denselben Listener verweisen.
Die Nichtverwendung
WeakReference
entspricht einem hohen Risiko, unnötigen Speicher und CPU zu verbrauchen. Es ist komplizierter, kniffliger und erfordert mehr Arbeit mit harten Referenzen im komplexen Code.WeakReferences
sind voller Profis, weil sie automatisch aufgeräumt werden. Der einzige Nachteil ist, dass Sie nicht vergessen dürfen, an anderer Stelle in Ihrem Code eine feste Referenz aufzubewahren. In der Regel ist dies bei Objekten der Fall, die sich auf diesen Listener verlassen.Ich hasse Code, der anonyme Klasseninstanzen von Listenern erstellt (wie von Kirk Woll erwähnt), weil Sie diese Listener nach der Registrierung nicht mehr abmelden können. Sie haben keinen Hinweis darauf. Es ist wirklich schlecht, IMHO zu codieren.
Sie können auch
null
auf einen Listener verweisen, wenn Sie ihn nicht mehr benötigen. Sie müssen sich keine Sorgen mehr machen.quelle
Es gibt wirklich keine Profis. Eine schwache Referenz wird normalerweise für "optionale" Daten verwendet, z. B. für einen Cache, in dem Sie die Speicherbereinigung nicht verhindern möchten. Sie möchten nicht, dass der Müll Ihres Hörers gesammelt wird, sondern dass er weiter zuhört.
Aktualisieren:
Ok, ich glaube, ich hätte vielleicht herausgefunden, worauf du hinaus willst. Wenn Sie langlebigen Objekten kurzlebige Listener hinzufügen, kann die Verwendung einer schwachen Referenz von Vorteil sein. Wenn Sie beispielsweise PropertyChangeListeners zu Ihren Domänenobjekten hinzufügen, um den Status der GUI zu aktualisieren, die ständig neu erstellt wird, behalten die Domänenobjekte die GUIs bei, die sich aufbauen können. Stellen Sie sich einen großen Popup-Dialog vor, der ständig neu erstellt wird und dessen Listener über einen PropertyChangeListener auf ein Employee-Objekt verweist. Korrigieren Sie mich, wenn ich falsch liege, aber ich denke nicht, dass das gesamte PropertyChangeListener-Muster mehr sehr beliebt ist.
Wenn Sie dagegen über Listener zwischen GUI-Elementen sprechen oder Domänenobjekte GUI-Elemente abhören, kaufen Sie nichts, da die Listener dies auch tun, wenn die GUI nicht mehr angezeigt wird.
Hier sind ein paar interessante Lektüren:
http://www.javalobby.org/java/forums/t19468.html
Wie können Speicherlecks im Swing-Listener behoben werden?
quelle
Um ehrlich zu sein, kaufe ich diese Idee nicht wirklich und genau das, was Sie von einem addWeakListener erwarten. Vielleicht bin es nur ich, aber es scheint eine falsche gute Idee zu sein. Anfangs ist es verführerisch, aber die damit verbundenen Probleme sind nicht zu vernachlässigen.
Mit schwachReferenz sind Sie nicht sicher, ob der Listener nicht mehr aufgerufen wird, wenn auf den Listener selbst nicht mehr verwiesen wird. Der Garbage Collector kann einige ms später oder nie Menmory freigeben. Dies bedeutet, dass es möglicherweise weiterhin CPU verbraucht und dies seltsam macht, wie das Auslösen einer Ausnahme, da der Listener nicht aufgerufen werden soll.
Ein Beispiel für Swing wäre der Versuch, Dinge zu tun, die Sie nur tun können, wenn Ihre UI-Komponente tatsächlich an ein aktives Fenster angehängt ist. Dies kann eine Ausnahme auslösen und sich auf den Absturz des Benachrichtigers auswirken und verhindern, dass gültige Listener benachrichtigt werden.
Das zweite Problem ist, wie bereits erwähnt, der anonyme Hörer, er könnte zu früh freigegeben werden, nie oder nur ein paar Mal benachrichtigt werden.
Was Sie erreichen möchten, ist gefährlich, da Sie nicht mehr kontrollieren können, wann Sie keine Benachrichtigungen mehr erhalten. Sie können ewig dauern oder zu früh aufhören.
quelle
Da Sie den WeakReference-Listener hinzufügen, verwenden Sie vermutlich ein benutzerdefiniertes Observable-Objekt.
In der folgenden Situation ist es absolut sinnvoll, eine WeakReference für ein Objekt zu verwenden. - Das Observable-Objekt enthält eine Liste von Listenern. - Sie haben bereits einen harten Bezug zu den Zuhörern woanders. (Sie müssten sich dessen sicher sein) - Sie möchten nicht, dass der Garbage Collector die Listener nicht mehr löscht, nur weil im Observable ein Verweis darauf vorhanden ist. - Während der Speicherbereinigung werden die Listener gelöscht. In der Methode, mit der Sie die Listener benachrichtigen, löschen Sie die WeakReference-Objekte aus der Benachrichtigungsliste.
quelle
Meiner Meinung nach ist es in den meisten Fällen eine gute Idee. Der Code, der für die Freigabe des Listeners verantwortlich ist, befindet sich an derselben Stelle, an der er registriert wird.
In der Praxis sehe ich eine Menge Software, die die Hörer für immer hält. Oft sind sich Programmierer nicht einmal bewusst, dass sie die Registrierung aufheben sollten.
In der Regel ist es möglich, ein benutzerdefiniertes Objekt mit einem Verweis auf den Listener zurückzugeben, mit dem manipuliert werden kann, wann die Registrierung aufgehoben werden muss. Zum Beispiel:
listeners.on("change", new Runnable() { public void run() { System.out.println("hello!"); } }).keepFor(someInstance).keepFor(otherInstance);
Dieser Code würde den Listener registrieren, ein Objekt zurückgeben, das den Listener kapselt und über die Methode keepFor verfügt, die den Listener zu einer statischen schwachHashMap mit dem Instanzparameter als Schlüssel hinzufügt. Dies würde garantieren, dass der Listener mindestens so lange registriert ist, wie someInstance und otherInstance nicht durch Müll gesammelt werden.
Es kann andere Methoden wie keepForever () oder keepUntilCalled (5) oder keepUntil (DateTime.now (). PlusSeconds (5)) oder unregisterNow () geben.
Die Standardeinstellung kann für immer beibehalten werden (bis sie nicht registriert ist).
Dies könnte auch ohne schwache Referenzen implementiert werden, jedoch ohne Phantomreferenzen, die das Entfernen des Listeners auslösen.
edit: hat eine kleine Bibliothek erstellt, die eine Basisversion dieses Ansatzes implementiert: https://github.com/creichlin/struwwel
quelle
on()
aber vorher erfolgtkeepFor()
? VermutlichkeepFor()
AnrufeweakReference.get()
, die möglicherweisenull
schon zu diesem Zeitpunkt zurückkehren?Ich kann mir keinen legitimen Anwendungsfall für die Verwendung von WeakReferences für Listener vorstellen, es sei denn, Ihr Anwendungsfall umfasst Listener, die nach dem nächsten GC-Zyklus explizit nicht mehr existieren sollten (dieser Anwendungsfall wäre natürlich VM- / plattformspezifisch).
Es ist möglich, sich einen etwas legitimeren Anwendungsfall für SoftReferences vorzustellen, bei dem die Listener optional sind, aber viel Heap beanspruchen und der erste sein sollten, der anfängt, wenn die Größe des freien Heaps anfängt, heikel zu werden. Ich nehme an, eine Art optionales Caching oder eine andere Art von assistierendem Hörer könnte ein Kandidat sein. Selbst dann scheint es so, als ob Sie möchten, dass die Interna der Listener die SoftReferences verwenden, nicht die Verbindung zwischen Listener und Listenee.
Wenn Sie jedoch ein dauerhaftes Listener-Muster verwenden, sind die Listener nicht optional. Daher kann das Stellen dieser Frage ein Symptom sein, das Sie benötigen, um Ihre Architektur zu überdenken.
Ist dies eine akademische Frage oder haben Sie eine praktische Situation, die Sie ansprechen möchten? Wenn es sich um eine praktische Situation handelt, würde ich gerne hören, was es ist - und Sie könnten wahrscheinlich mehr und weniger abstrakte Ratschläge zur Lösung erhalten.
quelle
Ich habe 3 Vorschläge für das Originalplakat. Es tut mir leid, dass ich einen alten Thread wiederbelebt habe, aber ich denke, meine Lösungen wurden zuvor in diesem Thread nicht besprochen.
Betrachten Sie zunächst das Beispiel von javafx.beans.values.WeakChangeListener in den JavaFX-Bibliotheken.
Zweitens habe ich das JavaFX-Muster durch Ändern der addListener-Methoden meines Observable verbessert. Die neue Methode addListener () erstellt jetzt Instanzen der entsprechenden WeakXxxListener-Klassen für mich.
Die Methode "Feuerereignis" konnte leicht geändert werden, um die XxxWeakListener zu dereferenzieren und zu entfernen, wenn WeakReference.get () null zurückgab.
Die Methode zum Entfernen war jetzt etwas unangenehmer, da ich die gesamte Liste durchlaufen muss, und das bedeutet, dass ich eine Synchronisierung durchführen muss.
Drittens habe ich vor der Implementierung dieser Strategie eine andere Methode angewendet, die Sie vielleicht nützlich finden. Die (harten Referenz-) Zuhörer erhielten ein neues Ereignis, bei dem sie eine Realitätsprüfung durchführten, ob sie noch verwendet wurden oder nicht. Wenn nicht, haben sie sich vom Beobachter abgemeldet, wodurch sie GCed wurden. Für kurzlebige Zuhörer, die langlebige Observables abonniert haben, war es ziemlich einfach, Veralterung zu erkennen.
Aus Rücksicht auf die Leute, die festlegten, dass es "eine gute Programmierpraxis ist, Ihre Listener immer abzumelden, wenn ein Listener sich selbst abmeldet, habe ich einen Protokolleintrag erstellt und das Problem später in meinem Code behoben.
quelle
WeakListener sind in Situationen nützlich, in denen GC speziell die Lebensdauer des Listeners steuern soll.
Wie bereits erwähnt, ist dies eine andere Semantik als im üblichen Fall addListener / removeListener, sie ist jedoch in einigen Szenarien gültig.
Stellen Sie sich zum Beispiel einen sehr großen Baum vor, der spärlich ist - einige Knotenebenen sind nicht explizit definiert, können aber von übergeordneten Knoten weiter oben in der Hierarchie abgeleitet werden. Die implizit definierten Knoten hören auf die übergeordneten Knoten, die definiert sind, damit sie ihren impliziten / geerbten Wert auf dem neuesten Stand halten. Aber der Baum ist riesig - wir möchten nicht, dass implizite Knoten für immer verfügbar sind -, solange sie vom aufrufenden Code verwendet werden, plus möglicherweise einem LRU-Cache von einigen Sekunden, um zu vermeiden, dass dieselben Werte immer wieder geändert werden.
Hier ermöglicht der schwache Listener es untergeordneten Knoten, Eltern zuzuhören, während ihre Lebensdauer durch Erreichbarkeit / Caching bestimmt wird, sodass die Struktur nicht alle implizierten Knoten im Speicher beibehält.
quelle
Möglicherweise müssen Sie Ihren Listener auch mit einer WeakReference implementieren, wenn Sie die Registrierung an einem Ort aufheben, der nicht jedes Mal aufgerufen werden kann.
Ich erinnere mich an einige Probleme mit einem unserer benutzerdefinierten
PropertyChangeSupport
Listener, die in Zeilenansichten in unserer ListView verwendet wurden. Wir konnten keinen guten und zuverlässigen Weg finden, um die Registrierung dieser Listener aufzuheben. Daher schien die Verwendung eines WeakReference-Listeners die sauberste Lösung zu sein.quelle
Aus einem Testprogramm geht hervor, dass anonyme ActionListener nicht verhindern, dass ein Objekt durch Müll gesammelt wird:
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(); } } }
produziert:
quelle