Dans l'article précédent, j'ai introduit la méthode d'utilisation de Java8 pour implémenter le modèle d'observateur (partie 1). Cet article continue d'introduire les connaissances pertinentes du modèle d'observateur Java8. Le contenu spécifique est le suivant:
Implémentation de file
Le chapitre précédent présente la mise en œuvre du modèle d'observateur dans un environnement Java moderne. Bien qu'il soit simple mais complet, cette implémentation ignore un problème clé: la sécurité des threads. La plupart des applications Java ouvertes sont multipliées et le mode observateur est principalement utilisé dans les systèmes multi-thread ou asynchrones. Par exemple, si un service externe met à jour sa base de données, l'application recevra également un message de manière asynchrone, puis informera le composant interne pour mettre à jour en mode observateur, au lieu d'enregistrer et d'écouter directement le service externe.
La sécurité des threads en mode observateur est principalement axée sur le corps du mode, car les conflits de threads sont susceptibles de modifier la collection d'écoute enregistrée. Par exemple, un thread essaie d'ajouter un nouvel auditeur, tandis que l'autre thread essaie d'ajouter un nouvel objet animal, qui déclenche des notifications à tous les auditeurs enregistrés. Compte tenu de l'ordre de séquence, le premier thread peut avoir terminé l'enregistrement du nouvel écouteur avant que l'auditeur enregistré ne reçoive la notification de l'animal ajouté. Il s'agit d'un cas classique de concours de ressources de fil, et c'est ce phénomène qui indique aux développeurs qu'ils ont besoin d'un mécanisme pour assurer la sécurité des filetages.
La solution la plus simple à ce problème est: toutes les opérations qui accèdent ou modifient la liste des auditeurs d'enregistrement doivent suivre le mécanisme de synchronisation Java, tel que:
Public synchronisé AnimalAddedListener RegisteranimalAddedListener (AnimalAddedListener Écouteur) {/*...*/} public synchronisé void UnregistanImalAddedListener (AnimalAddedener ÉcoucteurDe cette façon, en même temps, un seul thread peut modifier ou accéder à la liste des auditeurs enregistrés, qui peuvent réussir à éviter les problèmes de concurrence des ressources, mais de nouveaux problèmes surviennent, et ces contraintes sont trop strictes (pour plus d'informations sur les mots clés synchronisés et les modèles de concurrence Java, veuillez vous référer à la page Web officielle). Grâce à la synchronisation des méthodes, un accès simultané à la liste des auditeurs peut être observé à tout moment. L'enregistrement et la révocation de l'auditeur est une opération d'écriture pour la liste des auditeurs, tout en notifiant l'auditeur pour accéder à la liste des auditeurs est une opération en lecture seule. Étant donné que l'accès via la notification est une opération de lecture, plusieurs opérations de notification peuvent être effectuées simultanément.
Par conséquent, tant qu'il n'y a pas d'enregistrement ou de révocation de l'auditeur, tant que l'enregistrement n'est pas enregistré, tant qu'un certain nombre de notifications concurrentes peuvent être exécutées simultanément sans déclencher une concurrence de ressources pour la liste des auditeurs enregistrés. Bien sûr, le concours de ressources dans d'autres situations existe depuis longtemps. Afin de résoudre ce problème, le verrouillage des ressources pour ReadWriteLock est conçu pour gérer les opérations de lecture et d'écriture séparément. Le code d'implémentation ThreadSaFezOO à filetage de la classe de zoo est le suivant:
classe publique ThreadSaFEZOO {private final readWriteLock readWriteLock = new ReentRanTreadWriteLock (); Lock final protégé readlock = readWriteLock.readlock (); Protégé de verrouillage final writeLock = readWriteLock.WriteLock (); Liste privée <Animal> Animals = New ArrayList <> (); Liste privée <AnimalAddeDListener> auditeurs = New ArrayList <> (); public void addanimal (Animal Animal) {// Ajouter la liste des animaux des auditersthis.notifyanimaladdedListeners (Animal);} public animalAddededListener registeranimaladdedenener (AnimalAddeDistener auteur) {// verrouiller la liste des auditeurs pour écrire ceci.writelock.lock (); essayez {// ajouter l'auditeur à la liste des auteurs enregistrés. lockthis.writelock.unlock ();} return auditeur;} public void UnregistanimalAddeDListener (AnimalAdDistener Écouteur) {// Lock la liste des auditeurs de la liste de la liste des écouteurs enregistrés. Lockthis.WriteLock.Unlock ();}} public void notifyanimaladdedListeners (Animal Animal) {// Lock la liste des auditeurs pour ReadingThis.readlock.lock (); try {// notifiez chacun des auditeurs dans la liste des auditeurs inscritsthis.SENSENERS.Forach (auditeur -> écouteur.UpDateanImalAdded (animal); Déverrouillez le lecteur Lockthis.readlock.unlock ();}}}Grâce à un tel déploiement, la mise en œuvre du sujet peut garantir la sécurité des threads et plusieurs threads peuvent émettre des notifications en même temps. Mais malgré cela, il y a encore deux problèmes de concurrence en matière de ressources qui ne peuvent pas être ignorés:
Accès simultané à chaque auditeur. Plusieurs threads peuvent informer l'auditeur que de nouveaux animaux sont nécessaires, ce qui signifie qu'un auditeur peut être appelé par plusieurs threads en même temps.
Accès simultané à la liste des animaux. Plusieurs threads peuvent ajouter des objets à la liste des animaux en même temps. Si l'ordre des notifications a un impact, cela peut conduire à une concurrence sur les ressources, ce qui nécessite un mécanisme de traitement des opérations simultanées pour éviter ce problème. Si la liste des auditeurs enregistrés reçoit une notification pour ajouter Animal2, puis reçoit une notification pour ajouter Animal1, une compétition de ressources se produira. Cependant, si l'ajout d'Animal1 et Anims2 est effectué par différents fils, il est également possible de compléter l'ajout d'Animal1 avant Anims2. Plus précisément, Thread 1 ajoute Animal1 avant de notifier l'auditeur et verrouille le module, Thread 2 ajoute Animal2 et informe l'auditeur, puis le thread 1 informe l'auditeur qu'Animal1 a été ajouté. Bien que la concurrence des ressources puisse être ignorée lorsque l'ordre de séquence n'est pas pris en compte, le problème est réel.
Accès simultané aux auditeurs
Les auditeurs d'accès simultanément peuvent être mis en œuvre en assurant la sécurité des fils des auditeurs. Adhérant à l'esprit de «l'auto-responsabilité» de la classe, l'auditeur a l'obligation d'assurer sa propre sécurité de fil. Par exemple, pour l'auditeur compté ci-dessus, l'augmentation ou la diminution du nombre d'animaux par plusieurs threads peut entraîner des problèmes de sécurité des filetages. Pour éviter ce problème, le calcul des nombres animaux doit être des opérations atomiques (variables atomiques ou synchronisation de la méthode). Le code de solution spécifique est le suivant:
classe publique ThreadSaFecountingAnAmalAddededener implémente AnimalAddedListener {private static atomiclong animauxAddedCount = new Atomiclong (0); @ OverRidepublic void updateanimaladded (animal animal) {// incrément le nombre d'animaux);Le code de solution de synchronisation de la méthode est le suivant:
classe publique CountinganimalAddedListener implémente AnimalAddedListener {private static int animauxaddedcount = 0; @OverridePublic synchronisé void updateanimaladded (animal animal) {// incrément le nombre d'animaux.Il convient de souligner que l'auditeur devrait assurer sa propre sécurité de fil. Le sujet doit comprendre la logique interne de l'auditeur, plutôt que de simplement garantir la sécurité des fils pour accéder et modifier l'auditeur. Sinon, si plusieurs sujets partagent le même auditeur, chaque classe de sujet doit réécrire du code de filetage. De toute évidence, un tel code n'est pas assez concis, donc Thread-Safe doit être implémenté dans la classe d'écoute.
Notifications ordonnées des auditeurs
Lorsque l'auditeur doit exécuter de manière ordonnée, le verrouillage de lecture et d'écriture ne peut pas répondre aux besoins, et un nouveau mécanisme doit être introduit pour s'assurer que l'ordre d'appel de la fonction de notification est cohérent avec l'ordre dans lequel l'animal est ajouté au zoo. Certaines personnes ont essayé de l'implémenter en utilisant la synchronisation de la méthode, mais selon l'introduction de la synchronisation de la méthode dans la documentation Oracle, on peut voir que la synchronisation des méthodes ne fournit pas la gestion des commandes de l'exécution de l'opération. Il garantit seulement que les opérations atomiques ne sont pas interrompues et ne garantissent pas l'ordre du thread de l'exécution du premier arrivé, premier (FIFO). ReentRenTreadWriteLock peut implémenter un tel ordre d'exécution, le code est le suivant:
classe publique OrderEdThreadSaFeZoo {private final readWritelock readWriteLock = new ReentRanTreadWriteLock (true); Lock final protégé readlock = readWriteLock.readlock (); Protégé de verrouillage final writeLock = readWriteLock.WriteLock (); Liste privée <Animal> Animals = New ArrayList <> (); Liste privée <AnimalAddeDListener> auditeurs = New ArrayList <> (); public void addanimal (Animal Animal) {// Ajouter la liste des animaux des auditersthis.notifyanimaladdedListeners (Animal);} public animalAddededListener registeranimaladdedenener (AnimalAddeDistener auteur) {// verrouiller la liste des auditeurs pour écrire ceci.writelock.lock (); essayez {// ajouter l'auditeur à la liste des auteurs enregistrés. lockthis.writelock.unlock ();} return auditeur;} public void UnregistanimalAddeDListener (AnimalAdDistener Écouteur) {// Lock la liste des auditeurs de la liste de la liste des écouteurs enregistrés. Lockthis.WriteLock.Unlock ();}} public void notifyanimaladdedListeners (Animal Animal) {// Lock la liste des auditeurs pour ReadingThis.readlock.lock (); try {// notifiez chacun des auditeurs dans la liste des auditeurs inscritsthis.SENSENERS.Forach (auditeur -> écouteur.UpDateanImalAdded (animal); Déverrouillez le lecteur Lockthis.readlock.unlock ();}}}De cette manière, les fonctions d'enregistrement, de désinscription et de notification obtiendront des autorisations de lecture et d'écriture de verrouillage dans l'ordre du premier entrée (FIFO). Par exemple, Thread 1 enregistre un auditeur, Thread 2 essaie d'informer l'auditeur enregistré après avoir démarré l'opération d'enregistrement, Thread 3 essaie d'informer l'auditeur enregistré lorsque Thread 2 attend le verrouillage en lecture seule, l'adoptant de la méthode d'ordre équitable, le thread 1 termine d'abord l'opération d'enregistrement, puis Thread 2 peut informer l'auditeur, et enfin le thread 3 NOTIFS L'écoute. Cela garantit que l'ordre d'exécution et l'ordre de départ de l'action sont cohérents.
Si la synchronisation de la méthode est adoptée, bien que le thread 2 fasse d'abord la file d'attente pour occuper les ressources, le thread 3 peut toujours obtenir le verrouillage des ressources avant le thread 2, et il ne peut pas être garanti que le thread 2 informe d'abord l'auditeur que le thread 3. La clé du problème est: la méthode d'ordre équitable peut garantir que les threads s'exécutent dans l'ordre dans lequel les ressources sont appliquées. Le mécanisme de commande des verrous en lecture et en écriture est très compliqué. Vous devez vous référer à la documentation officielle de ReentRanTreadWriteLock pour vous assurer que la logique du verrou est suffisante pour résoudre le problème.
La sécurité des threads a été mise en œuvre jusqu'à présent, et les avantages et les inconvénients de l'extraction de la logique du sujet et d'encapsulation de sa classe de mixin en unités de code reproductibles seront introduits dans les chapitres suivants.
Logique thème encapsulée à la classe de mixin
Il est très attrayant de résumer l'implémentation de conception de motifs d'observateurs mentionnés ci-dessus dans la classe de mélange cible. D'une manière générale, les observateurs en mode observateur contiennent une collection d'auditeurs enregistrés; enregistrer les fonctions responsables de l'enregistrement de nouveaux auditeurs; Les fonctions de désinscription responsables de la révocation des fonctions de non-enregistrement enregistrées et de notification des fonctions responsables de la notification des auditeurs. Pour l'exemple ci-dessus du zoo, toutes les autres opérations de la classe de zoo, sauf que la liste des animaux est requise pour le problème est d'implémenter la logique du sujet.
Le cas de la classe de mixin est illustré ci-dessous. Il convient de noter que pour rendre le code plus concis, le code sur la sécurité des threads est supprimé ici:
Classe de résumé public ObservableSubjectMixin <oucinerType> {private list <audunerType> écouteurs = new ArrayList <> (); public auteur de registre de registre (auditeur auditeur) {// Ajouter l'écouteur à la liste des auditeurs enregistrés iregisterAranAddeDDedener (écoutère). {// Supprimez l'auditeur de la liste des auditeurs inscritsthis.Listeners.remove (auditeur);} public void notifyListeners (Consumer <? Super auteurType> Algorithm) {// Exécuter une fonction sur chacun des auditersthis.Listeners.Forach (Algorithme);}}Étant donné que les informations d'interface du type d'auditeur enregistrées ne sont pas fournies, un écouteur spécifique ne peut pas être informé directement, il est donc nécessaire de garantir l'universalité de la fonction de notification et permettre au client d'ajouter certaines fonctions, telles que l'acceptation de la correspondance des paramètres des types de paramètres génériques applicables à chaque écouteur. Le code d'implémentation spécifique est le suivant:
La classe publique ZoousingMixin étend ObservableSubjectMixin <AnimalAddedListener> {List privé <Animal> Animals = new ArrayList <> (); public void addanimal (animal animal) {// Ajouter la liste des animaux. auditeur.updateanimaladded (animal));}}Le plus grand avantage de la technologie de classe de mixin est d'encapsuler le sujet à motif d'observateur dans une classe reproductible, plutôt que de répéter la logique dans chaque classe de matière. De plus, cette méthode rend la mise en œuvre de la classe de zoo plus simple, ne stockant que des informations sur les animaux sans considérer comment stocker et informer les auditeurs.
Cependant, l'utilisation de classes Mixin n'est pas seulement un avantage. Par exemple, que se passe-t-il si vous souhaitez stocker plusieurs types d'auditeurs? Par exemple, il est également nécessaire de stocker le type d'écouteur AnimalReMovedListener. La classe de mixin est une classe abstraite. Plusieurs classes abstraites ne peuvent pas être héritées en même temps en Java, et la classe de mixin ne peut pas être implémentée à la place d'une interface. En effet, l'interface ne contient pas d'état, et l'état du mode observateur doit être utilisé pour enregistrer la liste des auditeurs enregistrés.
Une solution consiste à créer un ZoListener de type auditeur qui sera informé lorsque les animaux augmenteront et diminuent. Le code ressemble à ceci:
Interface publique ZoListener {public void onanimaladded (animal animal); public vide onanimalremoved (animal animal);}De cette façon, vous pouvez utiliser cette interface pour implémenter la surveillance de divers changements dans l'état de zoo à l'aide d'un type d'écouteur:
La classe publique ZoousingMixin étend ObservablesBjectMixin <ZoListener> {private list <Animal> Animals = new ArrayList <> (); public void addanimal (animal animal) {// Ajouter l'animal à la liste des animaux. auditeur.onanimaladded (animal));} public void reroveanimal (animal animal) {// supprimer l'animal de la liste des animauxthis.animals.remove (animal); // notify la liste des écouteurs enregistrés.La combinaison de plusieurs types d'écoute dans une seule interface d'écoute résout le problème mentionné ci-dessus, mais il y a encore des lacunes, qui seront discutées en détail dans les chapitres suivants.
Écouteur et adaptateur multi-méthodes
Dans la méthode ci-dessus, si l'interface de l'écoute implémente trop de fonctions, l'interface sera trop verbeuse. Par exemple, Swing MouseListener contient 5 fonctions nécessaires. Bien que vous ne puissiez utiliser que l'un d'eux, vous devez ajouter ces 5 fonctions tant que vous utilisez l'événement de clic de souris. Plus susceptible d'utiliser des corps de fonction vides pour implémenter les fonctions restantes, ce qui apportera sans aucun doute une confusion inutile au code.
Une solution consiste à créer un adaptateur (le concept provient du modèle d'adaptateur proposé par GOF). Le fonctionnement de l'interface de l'écoute est implémenté sous la forme de fonctions abstraites pour l'héritage de la classe d'écoute spécifique. De cette façon, la classe d'écoute spécifique peut sélectionner les fonctions dont elle a besoin et utiliser les opérations par défaut pour les fonctions non nécessaires à l'adaptateur. Par exemple, dans la classe ZoListener dans l'exemple ci-dessus, Créez ZooAdapter (les règles de dénomination de l'adaptateur sont cohérentes avec l'auditeur, il vous suffit de changer l'auditeur du nom de la classe en adaptateur), le code est le suivant:
classe publique ZooAdapter implémente ZoListener {@Overridepublic void onanimaladded (animal animal) {} @Overridepublic void onanimalremoved (animal animal) {}}À première vue, cette classe d'adaptateur est insignifiante, mais la commodité qu'elle apporte ne peut pas être sous-estimée. Par exemple, pour les classes spécifiques suivantes, sélectionnez simplement les fonctions qui leur sont utiles:
classe publique NamePrinterzooAdapter étend ZooAdapter {@Overridepublic void onanimaladded (animal animal) {// imprime le nom de l'animal qui a été ajouté System.out.println ("Animal ajouté nommé" + animal.getName ());}}Il existe deux alternatives qui peuvent également implémenter les fonctions de la classe d'adaptateur: l'une consiste à utiliser la fonction par défaut; L'autre consiste à fusionner l'interface de l'écoute et la classe d'adaptateur dans une classe spécifique. La fonction par défaut est récemment proposée par Java 8, permettant aux développeurs de fournir des méthodes d'implémentation par défaut (défense) dans l'interface.
Cette mise à jour de la bibliothèque Java est principalement pour faciliter les développeurs pour implémenter les extensions de programme sans modifier l'ancienne version du code, donc cette méthode doit être utilisée avec prudence. Après l'avoir utilisé à plusieurs reprises, certains développeurs auront l'impression que le code écrit de cette manière n'est pas assez professionnel, et certains développeurs pensent que c'est la fonctionnalité de Java 8. Peu importe quoi, ils doivent comprendre quelle est l'intention initiale de cette technologie, puis décider de l'utiliser en fonction de questions spécifiques. Le code d'interface ZoListener implémenté à l'aide de la fonction par défaut est le suivant:
Interface publique ZoListener {Par défaut public void onanimaladded (animal animal) {} Par défaut public void onanimalRemoved (animal animal) {}}En utilisant des fonctions par défaut, l'implémentation des classes spécifiques de l'interface n'a pas besoin d'implémenter toutes les fonctions dans l'interface, mais implémente à la place les fonctions requises. Bien qu'il s'agisse d'une solution relativement simple au problème d'expansion de l'interface, les développeurs devraient prêter plus d'attention lors de l'utilisation.
La deuxième solution consiste à simplifier le mode observateur, à omettre l'interface de l'écoute et à utiliser des classes spécifiques pour implémenter les fonctions de l'écouteur. Par exemple, l'interface ZoListener devient la suivante:
classe publique ZoListener {public void onanimaladded (animal animal) {} public void onanimalremoved (animal animal) {}}Cette solution simplifie la hiérarchie du modèle d'observateur, mais elle n'est pas applicable à tous les cas, car si l'interface de l'écoute est fusionnée dans une classe spécifique, l'auditeur spécifique ne peut pas implémenter plusieurs interfaces d'écoute. Par exemple, si les interfaces AnimalAddedListener et AnimalRemovedListener sont écrites dans la même classe de béton, alors un seul auditeur spécifique ne peut pas implémenter les deux interfaces en même temps. De plus, l'intention de l'interface de l'auditeur est plus évidente que celle de la classe spécifique. Il est évident que le premier est de fournir des interfaces pour d'autres classes, mais le second n'est pas si évident.
Sans documentation appropriée, le développeur ne saura pas qu'il existe déjà une classe qui joue le rôle d'une interface et met en œuvre toutes ses fonctions correspondantes. De plus, le nom de classe ne contient pas d'adaptateurs car la classe ne rentre pas dans une certaine interface, donc le nom de classe n'implique pas spécifiquement cette intention. Pour résumer, un problème spécifique nécessite de choisir une méthode spécifique, et aucune méthode n'est omnipotente.
Avant de commencer le chapitre suivant, il est important de mentionner que les adaptateurs sont communs en mode observation, en particulier dans les anciennes versions du code Java. L'API Swing est implémentée sur la base des adaptateurs, car de nombreuses applications anciennes utilisent dans le modèle d'observateur dans Java 5 et Java 6. L'auditeur dans le cas du zoo peut ne pas nécessiter un adaptateur, mais il doit comprendre l'objectif de l'adaptateur et son application, car nous pouvons l'utiliser dans le code existant. Le chapitre suivant introduira les auditeurs provisoires. Ce type d'auditeur peut effectuer des opérations longues ou passer des appels asynchrones et ne peut pas immédiatement donner la valeur de retour.
Écouteur complexe et bloquant
Une hypothèse sur le modèle d'observateur est que lorsqu'une fonction est exécutée, une série d'auditeurs est appelée, mais il est supposé que ce processus est complètement transparent pour l'appelant. Par exemple, lorsque le code client ajoute un animal dans le zoo, il n'est pas connu qu'une série d'auditeurs sera appelée avant le succès du retour. Si l'exécution d'un auditeur prend beaucoup de temps (son temps est affecté par le nombre d'auditeurs, le temps d'exécution de chaque écouteur), le code client sera conscient des effets secondaires de cette simple augmentation des opérations animales.
Cet article ne peut pas discuter de ce sujet de manière complète. Voici les choses que les développeurs devraient prêter attention lors de l'appel des auditeurs complexes:
L'auditeur démarre un nouveau fil. Une fois le nouveau thread démarré, lors de l'exécution de la logique de l'écoute dans le nouveau thread, les résultats de traitement de la fonction d'écoute sont renvoyés et d'autres écouteurs sont exécutés.
Le sujet démarre un nouveau fil. Contrairement aux itérations linéaires traditionnelles des listes d'écouteurs enregistrées, la fonction Notifie de Subject redémarre un nouveau thread, puis itère la liste des écouteurs dans le nouveau fil. Cela permet à la fonction de notification de sortir sa valeur de retour tout en effectuant d'autres opérations d'auditeurs. Il convient de noter qu'un mécanisme de sécurité de fil est nécessaire pour garantir que la liste des auditeurs ne subit pas de modifications simultanées.
File d'attente appelle et exécute les fonctions d'écoute avec un ensemble de threads. Encapsuler les opérations de l'auditeur dans certaines fonctions et les faire filer au lieu d'un simple appel itératif à la liste des auditeurs. Une fois ces auditeurs stockés dans la file d'attente, le fil peut faire éclater un seul élément de la file d'attente et exécuter sa logique d'écoute. Ceci est similaire au problème du producteur-consommateur. Le processus de notification produit une file d'attente de fonctions exécutables, qui retirent ensuite la file d'attente et exécutent ces fonctions. La fonction doit stocker le temps où il a été créé plutôt que l'heure à laquelle il a été exécuté pour que la fonction de l'écoute puisse appeler. Par exemple, une fonction créée lorsque l'auditeur est appelé, alors la fonction doit stocker le moment. Cette fonction est similaire aux opérations suivantes en Java:
classe publique AnimalAddedFunctor {Écouteur privé Final AnimalAddedListener; Private Final Animal Paramètre; public AnimalAddedFunctor (AnimalAddededListener Écouteur, paramètre animal) {this.Listener = écouteur; this.paramètre = paramètre;} public Void Execcute () {// EXECUTE CreationThis.Listener.updateAnimalAdded (this.paramètre);}}Les fonctions sont créées et enregistrées dans une file d'attente et peuvent être appelées à tout moment, de sorte qu'il n'est pas nécessaire d'effectuer leurs opérations correspondantes immédiatement lors de la traversée de la liste de la liste. Une fois que chaque fonction qui active l'auditeur est poussée dans la file d'attente, le "thread de consommation" renverra les droits opérationnels sur le code client. Le "Thread Consumer" exécutera ces fonctions à un moment donné plus tard, comme si l'auditeur est activé par la fonction Notify. Cette technologie est appelée liaison des paramètres dans d'autres langues, qui convient à l'exemple ci-dessus. L'essence de la technologie consiste à enregistrer les paramètres de l'auditeur, puis à appeler directement la fonction EXECUTE (). Si l'auditeur reçoit plusieurs paramètres, la méthode de traitement est similaire.
Il convient de noter que si vous souhaitez enregistrer l'ordre d'exécution de l'auditeur, vous devez introduire un mécanisme de tri complet. Dans le schéma 1, l'auditeur active de nouveaux threads dans l'ordre normal, qui garantit que l'auditeur s'exécute dans l'ordre d'enregistrement. Dans le schéma 2, les files d'attente prennent en charge le tri, et les fonctions en elles seront exécutées dans l'ordre où ils entrent dans la file d'attente. En termes simples, les développeurs doivent prêter attention à la complexité de l'exécution multithread des auditeurs et le gérer attentivement pour s'assurer qu'ils implémentent les fonctions requises.
Conclusion
Avant que le modèle de l'observateur ne soit inscrit dans le livre en 1994, il s'agissait déjà d'un modèle de conception de logiciels grand public, offrant de nombreuses solutions satisfaisantes aux problèmes qui surviennent souvent dans la conception des logiciels. Java a toujours été un leader dans l'utilisation de ce modèle et résume ce modèle dans sa bibliothèque standard, mais étant donné que Java a été mis à jour vers la version 8, il est très nécessaire de réexaminer l'utilisation de modèles classiques. Avec l'émergence d'expressions de lambda et d'autres nouvelles structures, ce "vieux" schéma a pris une nouvelle vitalité. Qu'il s'agisse de gérer les anciens programmes ou d'utiliser cette méthode de longue date pour résoudre de nouveaux problèmes, en particulier pour les développeurs Java expérimentés, le modèle d'observateur est le principal outil pour les développeurs.
ONEAPM vous fournit des solutions de performance d'application Java de bout en bout. Nous prenons en charge tous les cadres Java et serveurs d'applications courants pour vous aider à découvrir rapidement les goulots d'étranglement du système et à localiser les causes profondes des anomalies. Déploiement à des niveaux minutieux et expérimentez instantanément, la surveillance Java n'a jamais été aussi simple. Pour lire plus d'articles techniques, veuillez visiter le blog technologique officiel d'OneAPM.
Le contenu ci-dessus vous découvre comment utiliser Java8 pour implémenter le mode observateur (partie 2), j'espère que cela sera utile à tout le monde!