java - patron - patterns design gof
Pros y contras de los oyentes como WeakReferences (12)
Como está agregando el oyente WeakReference, asumo que está utilizando un objeto Observable personalizado.
Tiene mucho sentido usar WeakReference para un objeto en la siguiente situación. - Hay una lista de oyentes en el objeto Observable. - Ya tiene una referencia difícil para los oyentes en otro lugar. (Debería estar seguro de esto) - No quiere que el recolector de basura deje de borrar a los oyentes solo porque hay una referencia en el Observable. - Durante la recolección de basura, los oyentes serán aclarados. En el método donde notificas a los oyentes, borras los objetos WeakReference de la lista de notificaciones.
¿Cuáles son los pros y los contras de mantener a los oyentes como WeakReferences?
El gran ''Pro'' por supuesto es que:
Agregar un oyente como una WeakReference significa que el oyente no necesita molestarse en "eliminarse".
Actualizar
Para aquellos preocupados por el oyente que tiene la única referencia al objeto, ¿por qué no puede haber 2 métodos, addListener () y addWeakRefListener ()?
aquellos que no se preocupan por la eliminación pueden usar lo último.
En mi opinión, es una buena idea en la mayoría de los casos. El código que es responsable de liberar al oyente se encuentra en el mismo lugar donde se registra.
En la práctica, veo un montón de software que mantiene a los oyentes para siempre. A menudo, los programadores ni siquiera son conscientes de que deben anular el registro.
Por lo general, es posible devolver un objeto personalizado con una referencia al oyente que permite la manipulación de cuándo anular el registro. Por ejemplo:
listeners.on("change", new Runnable() {
public void run() {
System.out.println("hello!");
}
}).keepFor(someInstance).keepFor(otherInstance);
este código registraría el oyente, devolvería un objeto que encapsula el oyente y tiene un método, keepFor que agrega al oyente a unHashMap débil estático con el parámetro de instancia como la clave. Eso garantizaría que el oyente esté registrado al menos mientras que una instancia y otra instancia no sean basura.
Puede haber otros métodos como keepForever () o keepUntilCalled (5) o keepUntil (DateTime.now (). PlusSeconds (5)) o unregisterNow ().
El valor predeterminado se puede mantener para siempre (hasta que no se registre).
Esto también podría implementarse sin referencias débiles, pero con referencias fantasmas que activan la eliminación del oyente.
editar: creó una pequeña lib que implementa una versión básica de este enfoque https://github.com/creichlin/struwwel
En primer lugar, el uso de WeakReference en las listas de oyentes le dará a su objeto una semántica diferente, y luego usará referencias difíciles. En el caso de referencia dura addListener (...) significa "notificar objeto suministrado sobre evento (s) específico hasta que lo detenga explícitamente con removeListener (..)", en caso de referencia débil significa "notificar objeto suministrado sobre evento específico ( s) hasta que este objeto no sea utilizado por nadie más (o se detenga explícitamente con removeListener) ". Tenga en cuenta que, en muchas situaciones, es perfectamente legal tener un objeto, escuchar algunos eventos y no tener otras referencias que lo mantengan fuera de GC. Logger puede ser un ejemplo.
Como puede ver, usar WeakReference no solo resuelve un problema ("debo tener en cuenta que no me olvide de eliminar el oyente agregado en alguna parte"), sino que también surge otro: "Debería tener en cuenta que mi oyente puede dejar de escuchar en cualquier momento en que ya no se hace referencia a eso ". No resuelves el problema, solo cambias un problema por otro. Mira, de cualquier forma que hayas forzado a definir claramente, diseñar y rastrear la vida de tu oyente, de una forma u otra.
Entonces, personalmente, estoy de acuerdo con mencionar qué uso WeakReference en las listas de oyentes es más como un hack que una solución. Es un patrón que vale la pena conocer, a veces puede ser útil, por ejemplo, para que el código heredado funcione bien. Pero no es un patrón de elección :)
PD También debe tenerse en cuenta que WeakReference introduce un nivel adicional de indirección, que, en algunos casos con tasas de eventos extremadamente altas, puede reducir el rendimiento.
Esta no es una respuesta completa, pero la misma fuerza que usted cita también puede ser su principal debilidad. Considere lo que sucedería si los oyentes de acción se implementaran débilmente:
button.addActionListener(new ActionListener() {
// blah
});
¡Ese oyente de acción recibirá basura en cualquier momento! No es raro que la única referencia a una clase anónima sea el evento al que la está agregando.
He visto toneladas de código donde los oyentes no se registraron correctamente. Esto significa que todavía fueron llamados innecesariamente para realizar tareas innecesarias.
Si solo una clase depende de un oyente, entonces es fácil de limpiar, pero ¿qué sucede cuando 25 clases confían en ella? Se vuelve mucho más complicado anular el registro correctamente. El hecho es que su código puede comenzar con un objeto que hace referencia a su oyente y terminar en una versión futura con 25 objetos que hacen referencia a ese mismo oyente.
No usar WeakReference
es equivalente a correr un gran riesgo de consumir memoria y CPU innecesarias. Es más complicado, más complicado y requiere más trabajo con referencias difíciles en el código complejo.
WeakReferences
están llenas de ventajas, ya que se limpian automáticamente. La única desventaja es que no debes olvidar guardar una referencia difícil en otro lugar de tu código. Típicamente, eso sería en objetos que dependen de este oyente.
Odio el código al crear instancias de audiencia anónimas de oyentes (como lo menciona Kirk Woll), porque una vez registrado, ya no puede anular el registro de estos oyentes. Usted no tiene una referencia a ellos. Es realmente malo la codificación en mi humilde opinión.
También puede null
una referencia a un oyente cuando ya no lo necesite. Ya no necesitas preocuparte más por eso.
Los WeakListeners son útiles en situaciones en las que desea específicamente que GC controle la vida del oyente.
Como se dijo anteriormente, esta es una semántica diferente, en comparación con el caso addListener / removeListener habitual, pero es válida en algunos escenarios.
Por ejemplo, considere un árbol muy grande, que es escaso: algunos niveles de nodos no están explícitamente definidos, pero se pueden inferir desde los nodos padre que están más arriba en la jerarquía. Los nodos definidos implícitamente escuchan los nodos principales que están definidos para que mantengan su valor implícito / heredado actualizado. Pero, el árbol es enorme, no queremos que los nodos implícitos estén disponibles para siempre, siempre que sean utilizados por el código de llamada, más quizás un caché LRU de unos segundos para evitar mezclar los mismos valores una y otra vez.
Aquí, el oyente débil hace posible que los nodos secundarios escuchen a los padres mientras que su vida útil se determina por accesibilidad / almacenamiento en caché para que la estructura no mantenga todos los nodos implicados en la memoria.
No puedo pensar en ningún caso de uso legítimo para usar WeakReferences para oyentes, a menos que de alguna manera su caso de uso involucre oyentes que explícitamente no deberían existir después del próximo ciclo de GC (ese caso de uso, por supuesto, sería VM / plataforma específica).
Es posible imaginar un caso de uso un poco más legítimo para SoftReferences, donde los oyentes son opcionales, pero ocupan mucho espacio y deberían ser los primeros en ir cuando el tamaño de montón libre comienza a ser arriesgado. Algún tipo de caché opcional u otro tipo de oyente asistente, supongo, podría ser un candidato. Incluso entonces parece que desearía que los internos de los oyentes utilizaran SoftReferences, no el vínculo entre el oyente y el oyente.
En general, si está utilizando un patrón de escucha persistente, los oyentes no son opcionales, por lo que hacer esta pregunta puede ser un síntoma de que necesita reconsiderar su arquitectura.
¿Es esta una pregunta académica, o tiene una situación práctica que está intentando abordar? Si se trata de una situación práctica, me encantaría saber qué es, y probablemente puedas obtener más consejos, menos abstractos, sobre cómo resolverlo.
Para ser sincero, realmente no compro esa idea y exactamente lo que esperas hacer con addWeakListener. Tal vez sea solo yo, pero parece ser una buena idea equivocada. Al principio es seductor, pero los problemas que puede implicar no son insignificantes.
Con weakReference no está seguro de que ya no se llamará al oyente cuando ya no se hace referencia al oyente. El recolector de basura puede liberar menmory unos pocos ms tarde o nunca. Esto significa que podría continuar consumiendo CPU y hacer que esto sea extraño, como lanzar una excepción porque no se debe llamar al oyente.
Un ejemplo con swing sería tratar de hacer cosas que solo puede hacer si su componente UI está realmente conectado a una ventana activa. Esto podría arrojar una excepción y afectar al notificador haciendo que se bloquee y evite que noten los oyentes válidos.
El segundo problema, como ya se mencionó, es que el oyente es anónimo, podrían ser liberados demasiado pronto y nunca se les notificará en absoluto, o solo unas pocas veces.
Lo que intentas lograr es peligroso ya que no puedes controlar más cuando dejas de recibir notificaciones. Pueden durar para siempre o detenerse demasiado pronto.
Realmente no hay profesionales. Por lo general, se utiliza una referencia débil para los datos "opcionales", como un caché en el que no se desea evitar la recolección de basura. No quiere que se recopile la basura de su oyente, quiere que siga escuchando.
Actualizar:
Ok, creo que podría haber descubierto a qué te refieres. Si está agregando oyentes efímeros a objetos de vida larga, puede ser beneficioso usar una referencia débil. Entonces, por ejemplo, si estuviera agregando PropertyChangeListeners a los objetos de su dominio para actualizar el estado de la GUI que se recrea constantemente, los objetos de dominio se mantendrán en las GUI, que podrían acumularse. Piense en un gran cuadro de diálogo emergente que se recrea constantemente, con una referencia de escucha de nuevo a un objeto Employee a través de PropertyChangeListener. Corrígeme si me equivoco, pero no creo que todo el patrón PropertyChangeListener ya sea muy popular.
Por otro lado, si está hablando de oyentes entre elementos de GUI u objetos de dominio que escuchan elementos de GUI, no comprará nada, ya que cuando la GUI se va, también lo harán los oyentes.
Aquí hay un par de lecturas interesantes:
http://www.javalobby.org/java/forums/t19468.html
¿Cómo resolver las pérdidas de memoria del oyente oscilante?
Según un programa de prueba, los ActionListeners anónimos no evitarán que un objeto sea recolectado como basura:
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();
}
}
}
produce:
start collect
end collect
first finalized
second finalized
end program
También es posible que necesite implementar su oyente con WeakReference si lo está anulando en algún lugar que no se garantiza que se llame siempre.
Me parece recordar que tuvimos algunos problemas con uno de nuestros oyentes PropertyChangeSupport
personalizados que se usaban dentro de las Vistas de fila en nuestro ListView. No pudimos encontrar una manera agradable y confiable de anular el registro de esos oyentes, por lo que usar un oyente WeakReference parecía ser la solución más limpia.
Tengo 3 sugerencias para el póster original. Perdón por resucitar un hilo viejo, pero creo que mis soluciones no fueron discutidas previamente en este hilo.
Primero, considere seguir el ejemplo de javafx.beans.values.WeakChangeListener en las bibliotecas JavaFX.
En segundo lugar, aumenté el patrón JavaFX modificando los métodos addListener de mi Observable. El nuevo método addListener () ahora crea instancias de las clases WeakXxxListener correspondientes para mí.
El método "evento de fuego" se modificó fácilmente para eliminar la referencia de los XxxWeakListeners y eliminarlos cuando WeakReference.get () devolvió el valor nulo.
El método de quitar ahora era un poco más desagradable ya que necesito iterar toda la lista, y eso significa que necesito hacer la sincronización.
Tercero, antes de implementar esta estrategia empleé un método diferente que puede serle útil. Los oyentes (de referencia) obtuvieron un nuevo evento que hicieron una verificación de la realidad de si todavía se usaban o no. De lo contrario, se desuscribieron del observador, lo que les permitió ser censados. Para los oyentes de corta vida suscritos a Observables de larga vida, la detección de la obsolescencia fue bastante fácil.
En deferencia a las personas que estipularon que era "una buena práctica de programación cancelar siempre la suscripción de sus oyentes, cada vez que un oyente recurría a darse de baja, me aseguré de crear una entrada de registro y corregí el problema en mi código más adelante.