In einer Bibliothek in Java 7 habe ich eine Klasse, die Dienste für andere Klassen bereitstellt. Nach dem Erstellen einer Instanz dieser Serviceklasse kann eine Methode davon mehrmals aufgerufen werden (nennen wir sie die doWork()
Methode). Ich weiß also nicht, wann die Arbeit der Serviceklasse abgeschlossen ist.
Das Problem ist, dass die Serviceklasse schwere Objekte verwendet und diese freigeben sollte. Ich habe diesen Teil in einer Methode festgelegt (nennen wir es release()
), aber es ist nicht garantiert, dass andere Entwickler diese Methode verwenden.
Gibt es eine Möglichkeit, andere Entwickler zu zwingen, diese Methode aufzurufen, nachdem die Aufgabe der Serviceklasse abgeschlossen wurde? Natürlich kann ich das dokumentieren, aber ich möchte sie zwingen.
Hinweis: Ich kann die release()
Methode in der doWork()
Methode nicht aufrufen , da doWork()
diese Objekte beim nächsten Aufruf benötigt werden.
AutoCloseable
wurde für genau diesen Zweck gemacht.Antworten:
Die pragmatische Lösung besteht darin, die Klasse zu erstellen
AutoCloseable
und einefinalize()
Methode als Backstop bereitzustellen (falls zutreffend ... siehe unten!). Dann verlassen Sie sich darauf, dass Benutzer Ihrer Klasse try-with-resource verwenden oderclose()
explizit anrufen .Leider gibt es in Java keine Möglichkeit 1 , den Programmierer dazu zu zwingen, das Richtige zu tun. Das Beste, was Sie hoffen können, ist, eine falsche Verwendung in einem statischen Code-Analysator zu erkennen.
Zum Thema Finalizer. Diese Java-Funktion hat nur sehr wenige gute Anwendungsfälle. Wenn Sie sich darauf verlassen, dass die Finalizer aufräumen, besteht das Problem, dass das Aufräumen sehr lange dauern kann. Die Finalizerthread wird nur dann ausgeführt werden , nachdem die GC entscheidet , dass das Objekt nicht mehr erreichbar ist . Dies kann nicht passieren, bis die JVM eine vollständige Sammlung durchführt.
Wenn das Problem, das Sie lösen möchten, darin besteht, Ressourcen zurückzugewinnen, die vorzeitig freigegeben werden müssen , haben Sie das Boot mit der Finalisierung verpasst.
Und nur für den Fall, dass Sie nicht haben, was ich oben sage ... es ist fast nie angebracht, Finalizer im Produktionscode zu verwenden, und Sie sollten sich niemals auf sie verlassen!
1 - Es gibt Wege. Wenn Sie zu „verstecken“ vorbereitet sind die Service - Objekte aus Benutzercode oder fest ihren Lebenszyklus steuern (zB https://softwareengineering.stackexchange.com/a/345100/172 ) , dann dem Benutzer - Code nicht brauchen , um Anruf
release()
. Die API wird jedoch komplizierter, eingeschränkter ... und "hässlicher" IMO. Dies zwingt den Programmierer auch nicht dazu, das Richtige zu tun . Es beseitigt die Fähigkeit des Programmierers, das Falsche zu tun!quelle
DefaultResource
undFinalizableResource
die erste erweitert und fügt einen Finalizer hinzu. In der Produktion verwenden Sie,DefaultResource
die keinen Finalizer-> keinen Overhead hat. Während der Entwicklung und des Testens konfigurieren Sie die zu verwendende App,FinalizableResource
die über einen Finalizer verfügt und somit zusätzlichen Aufwand verursacht. Während des Entwickelns und Testens ist dies kein Problem, aber es kann Ihnen helfen, Lecks zu identifizieren und zu beheben, bevor Sie die Produktion erreichen. Wenn einFinalizableResource
Leck PRD erreicht, können Sie die Fabrik so konfigurieren, dass X% erstellt werden, um das Leck zuDies scheint der Anwendungsfall für die
AutoCloseable
Schnittstelle und die in Java 7 eingeführtetry-with-resources
Anweisung zu seindann können Ihre Verbraucher es als
und müssen sich nicht darum kümmern, einen expliziten
release
Code aufzurufen, unabhängig davon, ob sein Code eine Ausnahme auslöst.Zusätzliche Antwort
Wenn Sie wirklich sicherstellen möchten, dass niemand vergessen kann, Ihre Funktion zu verwenden, müssen Sie einige Dinge tun
MyService
privat sind.MyService
, der sicherstellt, dass er nach dem Bereinigen gelöscht wird.Du würdest sowas machen
Sie würden es dann als verwenden
Ich habe diesen Pfad ursprünglich nicht empfohlen, da er ohne Java-8-Lambdas und funktionale Schnittstellen (wo
MyServiceConsumer
wirdConsumer<MyService>
) abscheulich ist und für Ihre Verbraucher ziemlich imposant ist. Aber wenn Sie nur wollen, dassrelease()
aufgerufen werden muss, wird es funktionieren.quelle
try-with-resources
und nicht einfach weglassentry
und somit denclose
Anruf verpassen ?try-with-resources
?.close()
es aufgerufen wird, wenn die Klasse implementiert wirdAutoCloseable
.using
Block für die deterministische Finalisierung verbinden? Zumindest in C # wird dies zu einem ziemlich klaren Muster für Entwickler, die sich angewöhnen, Ressourcen zu nutzen, die deterministisch finalisiert werden müssen. Etwas Leichtes und Gewohnheitsbildendes ist das Nächstbeste, wenn man jemanden nicht zwingen kann, etwas zu tun. stackoverflow.com/a/2016311/84206Ja, du kannst
Sie können sie zwingen, neue
Service
Instanzen zu erstellen, und dies zu 100% unmöglich machen, indem Sie es ihnen unmöglich machen, neue Instanzen zu erstellen .Wenn sie so etwas schon einmal gemacht haben:
Ändern Sie es dann so, dass sie so darauf zugreifen müssen.
Vorsichtsmaßnahmen:
AutoCloseable
jemandem erwähnten Schnittstelle. Aber das gegebene Beispiel sollte sofort funktionieren, und abgesehen von der Eleganz (die ein schönes Ziel für sich ist) sollte es keinen wichtigen Grund geben, es zu ändern. Beachten Sie, dass dies bedeuten soll, dass Sie esAutoCloseable
in Ihrer privaten Implementierung verwenden können. Der Vorteil meiner Lösung gegenüber der Verwendung durch Ihre BenutzerAutoCloseable
besteht darin, dass sie nicht vergessen werden kann.new Service
Aufruf einzufügen.Service
) in der Hand des Aufrufers liegt oder nicht, außerhalb des Bereichs dieser Antwort. Aber wenn Sie sich dazu entschließen, dass Sie das unbedingt brauchen, können Sie es so machen.quelle
Sie können versuchen, das Befehlsmuster zu verwenden.
Dies gibt Ihnen die volle Kontrolle über die Beschaffung und Freigabe von Ressourcen. Der Anrufer übergibt nur Aufgaben an Ihren Dienst, und Sie sind selbst dafür verantwortlich, Ressourcen zu beschaffen und zu bereinigen.
Es gibt jedoch einen Haken. Der Anrufer kann dies trotzdem tun:
Aber Sie können dieses Problem ansprechen, wenn es auftritt. Wenn es Leistungsprobleme gibt und Sie feststellen, dass einige Anrufer dies tun, zeigen Sie ihnen einfach den richtigen Weg und beheben Sie das Problem:
Sie können dies beliebig komplex gestalten, indem Sie Versprechungen für Aufgaben implementieren, die von anderen Aufgaben abhängig sind. Das Freigeben von Inhalten wird jedoch auch komplizierter. Dies ist nur ein Ausgangspunkt.
Async
Wie in den Kommentaren ausgeführt wurde, funktioniert das oben Genannte nicht wirklich mit asynchronen Anforderungen. Das ist richtig. Dies kann in Java 8 mit CompleteableFuture problemlos gelöst werden , insbesondere
CompleteableFuture#supplyAsync
um die einzelnen Zukünfte zu erstellen undCompleteableFuture#allOf
die Freigabe der Ressourcen durchzuführen, sobald alle Aufgaben abgeschlossen sind. Alternativ kann man jederzeit Threads oder ExecutorServices verwenden, um eine eigene Implementierung von Futures / Promises durchzuführen.quelle
Wenn Sie können, erlauben Sie dem Benutzer nicht, die Ressource zu erstellen. Lassen Sie den Benutzer ein Besucherobjekt an Ihre Methode übergeben, wodurch die Ressource erstellt, an die Methode des Besucherobjekts übergeben und anschließend freigegeben wird.
quelle
AutoCloseable
macht eine sehr ähnliche Sache hinter den Kulissen. Unter einem anderen Gesichtspunkt ähnelt es der Abhängigkeitsinjektion .Meine Idee war ähnlich wie die von Polygnome.
Mit den Methoden "do_something ()" in Ihrer Klasse können Sie einfach Befehle zu einer privaten Befehlsliste hinzufügen. Dann haben Sie eine "commit ()" - Methode, die tatsächlich die Arbeit erledigt und "release ()" aufruft.
Wenn der Benutzer also niemals commit () aufruft, ist die Arbeit nie erledigt. In diesem Fall werden die Ressourcen freigegeben.
Sie können es dann wie folgt verwenden: foo.doSomething.doSomethineElse (). Commit ();
quelle
Eine andere Alternative zu den genannten wäre die Verwendung von "intelligenten Referenzen", um Ihre gekapselten Ressourcen so zu verwalten, dass der Garbage Collector automatisch bereinigen kann, ohne Ressourcen manuell freizugeben. Ihr Nutzen hängt natürlich von Ihrer Implementierung ab. Lesen Sie mehr zu diesem Thema in diesem wunderbaren Blog-Beitrag: https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
quelle
Für jede andere klassenorientierte Sprache würde ich sagen, legen Sie sie in den Destruktor, aber da dies keine Option ist, können Sie die Finalisierungsmethode überschreiben. Auf diese Weise wird die Instanz freigegeben, wenn sie außerhalb des Gültigkeitsbereichs liegt und der Speicherplatz nicht mehr ausreicht.
Ich bin mit Java nicht allzu vertraut, denke aber, dass dies der Zweck ist, für den finalize () vorgesehen ist.
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize ()
quelle
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.