Dans une bibliothèque Java 7, j'ai une classe qui fournit des services à d'autres classes. Après avoir créé une instance de cette classe de service, une de ses méthodes peut être appelée plusieurs fois (appelons-la la doWork()
méthode). Donc, je ne sais pas quand le travail de la classe de service est terminé.
Le problème est que la classe de service utilise des objets lourds et qu'elle devrait les libérer. J'ai mis cette partie dans une méthode (appelons-la release()
), mais il n'est pas garanti que d'autres développeurs l'utilisent.
Existe-t-il un moyen de forcer les autres développeurs à appeler cette méthode après avoir terminé la tâche de classe de service? Bien sûr, je peux documenter cela, mais je veux les forcer.
Remarque: Je ne peux pas appeler la release()
méthode dans la doWork()
méthode, car elle a doWork()
besoin de ces objets lors de son appel suivant.
AutoCloseable
a été fait dans ce but précis.Réponses:
La solution pragmatique consiste à faire la classe
AutoCloseable
et à fournir unefinalize()
méthode de protection (si nécessaire ... voir ci-dessous!). Ensuite, vous comptez sur les utilisateurs de votre classe pour utiliser try-with-resource ou appelerclose()
explicitement.Malheureusement, il n'y a aucun moyen 1 en Java pour forcer le programmeur à faire la bonne chose. Le mieux que vous puissiez espérer est de détecter une utilisation incorrecte dans un analyseur de code statique.
Au sujet des finaliseurs. Cette fonctionnalité Java a très peu de bons cas d'utilisation. Si vous comptez sur les finaliseurs pour ranger, le problème est que cela peut prendre beaucoup de temps pour le ranger. Le finaliseur ne sera exécuté qu'une fois que le GC aura décidé que l'objet n'est plus accessible. Cela ne se produira peut-être pas tant que la machine virtuelle Java ne réalisera pas une collection complète .
Ainsi, si le problème que vous tentez de résoudre consiste à récupérer les ressources qui doivent être libérées rapidement , vous avez manqué le coche en utilisant la finalisation.
Et juste au cas où vous n’auriez pas ce que je dis plus haut ... il n’est presque jamais approprié d’utiliser des finaliseurs dans le code de production, et vous ne devriez jamais vous fier à eux!
1 - Il y a des façons. Si vous êtes prêt à "masquer" les objets de service du code utilisateur ou à contrôler étroitement leur cycle de vie (par exemple, https://softwareengineering.stackexchange.com/a/345100/172 ), il n'est pas nécessaire d'appeler le code utilisateur
release()
. Cependant, l'API devient plus compliquée, plus restreinte ... et "moche" à l'OMI. En outre, cela ne force pas le programmeur à faire ce qui est bien . Cela supprime la capacité du programmeur à faire la mauvaise chose!la source
DefaultResource
etFinalizableResource
qui étend la première et ajoute un finaliseur. En production, vous utilisezDefaultResource
un finaliseur sans> frais généraux. Pendant le développement et les tests, vous configurez l'application à utiliserFinalizableResource
avec un finaliseur et donc une surcharge. Pendant le développement et les tests, ce n’est pas un problème, mais cela peut vous aider à identifier les fuites et à les réparer avant d’atteindre la production. Pourtant, si une fuite atteint PRD, vous pouvez configurer l’usine pour créer X%FinalizableResource
afin d’identifier la fuiteCela ressemble au cas d'
AutoCloseable
utilisation de l' interface et à latry-with-resources
déclaration introduite dans Java 7. Si vous avez quelque chose comme:alors vos consommateurs peuvent l'utiliser comme
et ne pas avoir à s'inquiéter d'appeler un explicite,
release
que leur code lève une exception ou non.Réponse supplémentaire
Si vous voulez réellement vous assurer que personne ne peut oublier d'utiliser votre fonction, vous devez faire quelques choses
MyService
sont privés.MyService
pour s’assurer qu’il est nettoyé après.Vous feriez quelque chose comme
Vous l'utiliseriez alors comme
À l'origine, je ne recommandais pas ce chemin car il est affreux sans les lambdas et interfaces fonctionnelles de Java-8 (où
MyServiceConsumer
devientConsumer<MyService>
) et il est assez imposant pour vos consommateurs. Mais si ce que vous voulez seulement, c'est qu'ilrelease()
faille l'appeler, cela fonctionnera.la source
try-with-resources
et non simplement omettre letry
, manquant ainsi l'close
appel?try-with-resources
?.close()
nom est appelé lorsque la classe implémenteAutoCloseable
.using
bloc pour la finalisation déterministe? Au moins en C #, cela devient un modèle assez clair pour que les développeurs prennent l'habitude de l'utiliser lorsqu'ils exploitent des ressources qui nécessitent une finalisation déterministe. Quelque chose de facile et de créer une habitude est la meilleure chose à faire lorsque vous ne pouvez pas forcer quelqu'un à faire quelque chose. stackoverflow.com/a/2016311/84206Oui, vous pouvez
Vous pouvez absolument les forcer à se libérer, et faire en sorte qu’il soit à 100% impossible à oublier, en leur rendant impossible la création de nouvelles
Service
instances.S'ils ont fait quelque chose comme ça avant:
Puis changez-le pour qu'ils puissent y accéder comme ceci.
Mises en garde:
AutoCloseable
interface mentionnée par quelqu'un; mais l'exemple donné devrait fonctionner immédiatement, et à l'exception de l'élégance (qui est un objectif agréable en soi), il ne devrait y avoir aucune raison majeure pour le changer. Notez que cela veut dire que vous pourriez utiliserAutoCloseable
dans votre implémentation privée; L'avantage de ma solution par rapport à l'utilisation de vos utilisateursAutoCloseable
est qu'elle peut, encore une fois, ne pas être oubliée.new Service
appel.Service
) appartient à la main de l’appelant ou non sort du cadre de cette réponse. Mais si vous décidez que vous en avez absolument besoin, voici comment vous pouvez le faire.la source
Vous pouvez essayer d'utiliser le modèle de commande .
Cela vous donne un contrôle total sur l'acquisition et la libération des ressources. L'appelant ne soumet que des tâches à votre service, et vous êtes responsable de l'acquisition et du nettoyage des ressources.
Il y a un piège, cependant. L'appelant peut toujours faire ceci:
Mais vous pouvez résoudre ce problème quand il y a une crise. Lorsqu'il y a des problèmes de performances et que vous découvrez que certains appelants le font, montrez-leur simplement la manière appropriée et résolvez le problème:
Vous pouvez rendre cette complexité arbitraire en mettant en œuvre des promesses pour des tâches qui dépendent d'autres tâches, mais la publication d'éléments devient également plus compliquée. Ceci n'est qu'un point de départ.
Async
Comme indiqué dans les commentaires, ce qui précède ne fonctionne pas vraiment avec les demandes asynchrones. C'est correct. Mais cela peut être facilement résolu dans Java 8 avec l’utilisation de CompleteableFuture , en particulier
CompleteableFuture#supplyAsync
pour créer des futurs individuels etCompleteableFuture#allOf
pour permettre la libération des ressources une fois que toutes les tâches sont terminées. Sinon, on peut toujours utiliser Threads ou ExecutorServices pour lancer leur propre implémentation de Futures / Promises.la source
Si vous le pouvez, n'autorisez pas l'utilisateur à créer la ressource. Laissez l'utilisateur passer un objet visiteur à votre méthode, ce qui créera la ressource, le transmettra à la méthode de l'objet visiteur et le libérera ensuite.
la source
AutoCloseable
fait une chose très similaire dans les coulisses. Sous un autre angle, c'est similaire à l' injection de dépendance .Mon idée était semblable à celle de Polygnome.
Vous pouvez avoir les méthodes "do_something ()" dans votre classe simplement ajouter des commandes à une liste de commandes privée. Ensuite, utilisez une méthode "commit ()" qui fait le travail et appelle "release ()".
Donc, si l'utilisateur n'appelle jamais commit (), le travail n'est jamais terminé. S'ils le font, les ressources sont libérées.
Vous pouvez ensuite l'utiliser comme foo.doSomething.doSomethineElse (). Commit ();
la source
Une autre alternative à celles mentionnées serait l'utilisation de "références intelligentes" pour gérer vos ressources encapsulées de manière à ce que le ramasse-miettes puisse nettoyer automatiquement sans libérer manuellement de ressources. Leur utilité dépend bien sûr de votre mise en œuvre. En savoir plus sur ce sujet dans ce merveilleux article de blog: https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
la source
Pour tout autre langage de classe, je dirais de le mettre dans le destructeur, mais comme ce n’est pas une option, je pense que vous pouvez remplacer la méthode finalize. Ainsi, lorsque l'instance sort du cadre et que les ordures sont collectées, elle sera publiée.
Je ne connais pas trop Java, mais je pense que c’est l’objectif auquel finalize () est destiné.
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize ()
la source
If you rely on finalizers to tidy up, you run into the problem that it can take a very long time for the tidy-up to happen. The finalizer will only be run after the GC decides that the object is no longer reachable. That may not happen until the JVM does a full collection.