web-dev-qa-db-fra.com

Avantages et inconvénients des auditeurs en tant que faiblesses

Quels sont les avantages et les inconvénients de garder les auditeurs en tant que références faibles.

Le grand "Pro" est bien sûr que:

L'ajout d'un écouteur en tant que référence faible signifie que l'auditeur n'a pas besoin de s'embêter à se "retirer" lui-même.

Mise à jour

Pour ceux qui s'inquiètent que l'auditeur ait la seule référence à l'objet, pourquoi ne peut-il pas y avoir 2 méthodes, addListener () et addWeakRefListener ()?

ceux qui ne se soucient pas du retrait peuvent utiliser ce dernier.

71
pdeva

Tout d'abord, l'utilisation de WeakReference dans les listes d'écoute donnera à votre objet un différent sémantique, puis en utilisant des références dures. Dans le cas de référence matérielle, addListener (...) signifie "notifier l'objet fourni à propos d'un ou de plusieurs événements spécifiques jusqu'à ce que je l'arrête explicitement avec removeListener (..)", dans le cas de référence faible, cela signifie " informer l'objet fourni des événements spécifiques jusqu'à ce que cet objet ne soit pas utilisé par quelqu'un d'autre (ou arrêter explicitement avec removeListener) ". Remarquez, il est parfaitement légal dans de nombreuses situations d'avoir un objet, d'écouter certains événements et de n'avoir aucune autre référence le gardant de GC. L'enregistreur peut être un exemple.

Comme vous pouvez le voir, l'utilisation de WeakReference ne résout pas seulement un problème ("Je dois garder à l'esprit pour ne pas oublier de supprimer l'auditeur ajouté quelque part"), mais aussi en augmenter un autre - "Je dois garder à l'esprit que mon auditeur peut arrêter d'écouter à tout moment moment où il n'y est plus fait référence ". Vous ne résolvez pas le problème, vous échangez simplement un problème contre un autre. Regardez, de quelque manière que ce soit vous avez forcé à définir, concevoir et tracer clairement la durée de vie de votre auditeur - d'une manière ou d'une autre.

Donc, personnellement, je suis d'accord pour mentionner que l'utilisation de WeakReference dans les listes d'auditeurs ressemble plus à un hack qu'à une solution. C'est un modèle qui mérite d'être connu, parfois il peut vous aider - pour faire fonctionner le code hérité, par exemple. Mais ce n'est pas un modèle de choix :)

P.S. Il convient également de noter ce que WeakReference introduit un niveau d'indirection supplémentaire qui, dans certains cas avec des taux d'événements extrêmement élevés, peut réduire les performances.

69
BegemoT

Ce n'est pas une réponse complète, mais la force même que vous citez peut aussi être sa principale faiblesse. Considérez ce qui se passerait si les écouteurs d'action étaient mis en œuvre faiblement:

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

Cet écouteur d'action va récupérer les ordures à tout moment! Il n'est pas rare que la seule référence à une classe anonyme soit l'événement auquel vous l'ajoutez.

31
Kirk Woll

J'ai vu des tonnes de code où les auditeurs n'étaient pas désinscrits correctement. Cela signifie qu'ils étaient toujours appelés inutilement pour effectuer des tâches inutiles.

Si une seule classe dépend d'un auditeur, il est facile de nettoyer, mais que se passe-t-il lorsque 25 classes en dépendent? Il devient beaucoup plus difficile de les désinscrire correctement. Le fait est que votre code peut commencer avec un objet référençant votre écouteur et se retrouver dans une future version avec 25 objets référençant ce même écouteur.

Ne pas utiliser WeakReference équivaut à prendre un gros risque de consommer de la mémoire et du CPU inutiles. C'est plus compliqué, plus délicat et nécessite plus de travail avec des références dures dans le code complexe.

WeakReferences sont pleins de pros, car ils sont nettoyés automatiquement. Le seul inconvénient est que vous ne devez pas oublier de garder une référence dure ailleurs dans votre code. En règle générale, cela se produirait dans les objets reposant sur cet écouteur.

Je déteste le code créant des instances de classe anonymes d'écouteurs (comme mentionné par Kirk Woll), car une fois enregistré, vous ne pouvez plus désinscrire ces écouteurs. Vous n'avez aucune référence à eux. Il est vraiment mauvais codage à mon humble avis.

Vous pouvez également null une référence à un écouteur lorsque vous n'en avez plus besoin. Vous n'avez plus à vous en soucier.

10

Il n'y a vraiment aucun avantage. Une référence faible est généralement utilisée pour les données "facultatives", comme un cache dans lequel vous ne voulez pas empêcher la récupération de place. Vous ne voulez pas que votre auditeur soit récupéré, vous voulez qu'il continue à écouter.

Mise à jour:

D'accord, je pense que j'aurais pu comprendre à quoi tu veux en venir. Si vous ajoutez des écouteurs de courte durée à des objets à longue durée de vie, il peut être avantageux d'utiliser une référence faible. Ainsi, par exemple, si vous ajoutiez PropertyChangeListeners à vos objets de domaine pour mettre à jour l'état de l'interface graphique qui est constamment recréée, les objets de domaine vont s'accrocher aux interfaces graphiques, qui pourraient s'accumuler. Pensez à une grande boîte de dialogue contextuelle qui est constamment recréée, avec une référence d'écouteur vers un objet Employee via un PropertyChangeListener. Corrigez-moi si je me trompe, mais je ne pense pas que l'ensemble du modèle PropertyChangeListener soit très populaire.

D'un autre côté, si vous parlez d'écouteurs entre des éléments d'interface graphique ou que des objets de domaine écoutent des éléments d'interface graphique, vous n'achèterez rien, car lorsque l'interface graphique disparaîtra, les auditeurs aussi.

Voici quelques lectures intéressantes:

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

Comment résoudre les fuites de mémoire de l'écouteur swing?

5
James Scriven

Pour être honnête, je n'achète pas vraiment cette idée et exactement ce que vous attendez d'un addWeakListener. C'est peut-être juste moi, mais cela semble être une mauvaise bonne idée. Au début, elle séduit mais les problèmes qu'elle pourrait impliquer ne sont pas négligeables.

Avec faibleRéférence, vous n'êtes pas sûr que l'écouteur ne sera plus appelé lorsque l'écouteur lui-même n'est plus référencé. Le garbage collector peut libérer la menmory quelques ms plus tard ou jamais. Cela signifie qu'il peut continuer à consommer du CPU et rendre étrange cela comme une exception de lancement car l'auditeur ne doit pas être appelé.

Un exemple avec swing serait d'essayer de faire des choses que vous ne pouvez faire que si votre composant d'interface utilisateur est réellement attaché à une fenêtre active. Cela pourrait lever une exception et affecter le notifiant, ce qui entraînerait son plantage et empêcherait la notification des écouteurs valides.

Le deuxième problème, comme déjà indiqué, est celui des auditeurs anonymes, ils pourraient être libérés trop tôt, jamais notifiés du tout ou seulement quelques fois.

Ce que vous essayez de réaliser est dangereux car vous ne pouvez plus contrôler lorsque vous cessez de recevoir des notifications. Ils peuvent durer éternellement ou s'arrêter trop tôt.

4
Nicolas Bousquet

Parce que vous ajoutez un écouteur WeakReference, je suppose que vous utilisez un objet Observable personnalisé.

Il est parfaitement logique d'utiliser une référence faible à un objet dans la situation suivante. - Il existe une liste d'auditeurs dans l'objet Observable. - Vous avez déjà une référence difficile aux auditeurs ailleurs. (vous devez en être sûr) - Vous ne voulez pas que le garbage collector arrête d'effacer les écouteurs juste parce qu'il y a une référence dans l'Observable. - Pendant la collecte des ordures, les auditeurs seront éclaircis. Dans la méthode où vous notifiez les écouteurs, vous effacez les objets WeakReference de la liste de notification.

3
Deepak

À mon avis, c'est une bonne idée dans la plupart des cas. Le code qui est responsable de la libération de l'auditeur se trouve au même endroit où il est enregistré.

Dans la pratique, je vois beaucoup de logiciels qui gardent les auditeurs pour toujours. Souvent, les programmeurs ne savent même pas qu'ils doivent les désinscrire.

Il est généralement possible de renvoyer un objet personnalisé avec une référence à l'écouteur qui permet de manipuler le moment de l'annulation de l'inscription. Par exemple:

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

ce code enregistrerait l'écouteur, renverrait un objet qui encapsule l'écouteur et possède une méthode, keepFor qui ajoute l'écouteur à un faiblesseHashMap statique avec le paramètre d'instance comme clé. Cela garantirait que l'auditeur est enregistré au moins aussi longtemps que someInstance et otherInstance ne sont pas récupérés.

Il peut y avoir d'autres méthodes comme keepForever () ou keepUntilCalled (5) ou keepUntil (DateTime.now (). PlusSeconds (5)) ou unregisterNow ().

La valeur par défaut peut être conservée pour toujours (jusqu'à ce qu'elle ne soit pas enregistrée).

Cela pourrait également être implémenté sans références faibles mais des références fantômes qui déclenchent la suppression de l'auditeur.

edit: créé une petite bibliothèque qui implémente une version de base de cette approche https://github.com/creichlin/struwwel

2
Christian

Je ne peux penser à aucun cas d'utilisation légitime pour utiliser WeakReferences pour les écouteurs, à moins que votre cas d'utilisation implique des écouteurs qui ne devraient explicitement pas exister après le prochain cycle de GC (ce cas d'utilisation, bien sûr, serait spécifique à la VM/plate-forme).

Il est possible d'envisager un cas d'utilisation légèrement plus légitime pour SoftReferences, où les écouteurs sont facultatifs, mais prennent beaucoup de tas et devraient être les premiers à partir lorsque la taille du tas libre commence à devenir risquée. Une sorte de mise en cache facultative ou un autre type d'auditeur assistant, je suppose, pourrait être un candidat. Même alors, il semble que vous souhaitiez que les internes des auditeurs utilisent les SoftReferences, pas le lien entre l'auditeur et l'auditeur.

En règle générale, si vous utilisez un modèle d'écoute persistant, les écouteurs ne sont pas facultatifs, donc poser cette question peut être un symptôme dont vous avez besoin pour reconsidérer votre architecture.

Est-ce une question académique, ou avez-vous une situation pratique que vous essayez de résoudre? Si c'est une situation pratique, j'aimerais entendre ce que c'est - et vous pourriez probablement obtenir des conseils plus, moins abstraits sur la façon de le résoudre.

2
jkraybill

J'ai 3 suggestions pour l'affiche originale. Désolé d'avoir ressuscité un ancien fil, mais je pense que mes solutions n'ont pas été discutées précédemment dans ce fil.

Tout d'abord, pensez à suivre l'exemple de javafx.beans.values.WeakChangeListener dans les bibliothèques JavaFX.

Deuxièmement, j'ai augmenté le modèle JavaFX en modifiant les méthodes addListener de mon Observable. La nouvelle méthode addListener () crée maintenant pour moi des instances des classes WeakXxxListener correspondantes.

La méthode "fire event" a été facilement modifiée pour déréférencer les XxxWeakListeners et les supprimer lorsque WeakReference.get () renvoyait null.

La méthode de suppression était maintenant un peu plus méchante car je dois itérer la liste entière, et cela signifie que je dois faire la synchronisation.

Troisièmement, avant de mettre en œuvre cette stratégie, j'ai utilisé une méthode différente qui peut vous être utile. Les auditeurs (référence difficile) ont eu un nouvel événement, ils ont fait une vérification de la réalité pour savoir s'ils étaient toujours utilisés. Sinon, ils se sont désabonnés de l'observateur, ce qui leur a permis d'être GCed. Pour les auditeurs de courte durée abonnés à des observables de longue durée, la détection de l'obsolescence était assez facile.

Par respect pour les gens qui ont stipulé que c'était "une bonne pratique de programmation de toujours se désinscrire de vos auditeurs, chaque fois qu'un auditeur recourait à se désinscrire lui-même, je m'assurais de créer une entrée de journal et corrigeait le problème dans mon code plus tard.

1
Teto

Les WeakListeners sont utiles dans les situations où vous souhaitez spécifiquement que GC contrôle la durée de vie de l'auditeur.

Comme indiqué précédemment, la sémantique est vraiment différente de celle du cas habituel addListener/removeListener, mais elle est valide dans certains scénarios.

Par exemple, considérons un très grand arbre, qui est clairsemé - certains niveaux de nœuds ne sont pas explicitement définis, mais peuvent être déduits des nœuds parents plus haut dans la hiérarchie. Les nœuds définis implicitement écoutent les nœuds parents qui sont définis afin de maintenir à jour leur valeur implicite/héritée. Mais, l'arborescence est énorme - nous ne voulons pas que les nœuds implicites soient éternels - tant qu'ils sont utilisés par le code appelant, plus peut-être un cache LRU de quelques secondes pour éviter de retourner les mêmes valeurs encore et encore.

Ici, l'écouteur faible permet aux nœuds enfants d'écouter les parents tout en décidant de leur durée de vie par accessibilité/mise en cache afin que la structure ne conserve pas tous les nœuds implicites en mémoire.

0
mdma

Il ressort d'un programme de test que des ActionListeners anonymes n'empêcheront pas un objet d'être récupéré:

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();
    }
}
}

produit:

start collect
end collect
first finalized
second finalized
end program
0
gerardw

Vous devrez peut-être également implémenter votre écouteur avec un WeakReference si vous le désinscrivez quelque part qui n'est pas garanti d'être appelé à chaque fois.

Il me semble que nous avons eu quelques problèmes avec l'un de nos écouteurs PropertyChangeSupport personnalisés qui étaient utilisés dans les vues de ligne de notre ListView. Nous n'avons pas pu trouver un moyen agréable et fiable de désinscrire ces auditeurs, donc utiliser un écouteur WeakReference semblait la solution la plus propre.

0
Dan J