W bibliotece w Javie 7 mam klasę, która świadczy usługi dla innych klas. Po utworzeniu instancji tej klasy usługi można wywołać kilka jej doWork()
metod (nazwijmy ją metodą). Nie wiem więc, kiedy zakończy się praca klasy usług.
Problem polega na tym, że klasa usług używa ciężkich przedmiotów i powinna je zwolnić. Ustawiłem tę część w metodzie (nazwijmy ją release()
), ale nie ma gwarancji, że inni programiści użyją tej metody.
Czy istnieje sposób zmuszenia innych programistów do wywołania tej metody po zakończeniu zadania klasy usług? Oczywiście mogę to udokumentować, ale chcę je zmusić.
Uwaga: Nie mogę wywołać release()
metody w doWork()
metodzie, ponieważ doWork()
potrzebuje tych obiektów, gdy zostanie wywołana w następnej kolejności.
AutoCloseable
został stworzony do tego właśnie celu.Odpowiedzi:
Pragmatycznym rozwiązaniem jest stworzenie klasy
AutoCloseable
i zapewnieniefinalize()
metody zabezpieczającej (w razie potrzeby ... patrz poniżej!). Następnie polegasz na użytkownikach swojej klasy, którzy używają try-with-resource lubclose()
jawnie dzwonią .Niestety, nie ma sposobu, 1 w języku Java, aby wymusić programista zrobić dobry uczynek. Najlepsze, co możesz zrobić, to wykryć nieprawidłowe użycie w statycznym analizatorze kodów.
Na temat finalistów. Ta funkcja Java ma bardzo niewiele dobrych przypadków użycia. Jeśli polegasz na finalizatorach, masz problem, że uporządkowanie może zająć bardzo dużo czasu. Finalizer będą działać tylko po GC decyduje, że obiekt nie jest już dostępne. Może się to nie zdarzyć, dopóki JVM nie wykona pełnej kolekcji.
Tak więc, jeśli problemem, który chcesz rozwiązać, jest odzyskanie zasobów, które trzeba wcześniej zwolnić , to przegapiłeś łódź, używając finalizacji.
I na wypadek, gdybyś nie miał tego, co mówię powyżej ... prawie nigdy nie jest właściwe używanie finalizatorów w kodzie produkcyjnym i nigdy nie powinieneś na nich polegać!
1 - Istnieją sposoby. Jeśli jesteś gotów „ukryć” obiekty usług przed kodem użytkownika lub ściśle kontrolować ich cykl życia (np. Https://softwareengineering.stackexchange.com/a/345100/172 ), to kod użytkownika nie musi dzwonić
release()
. Jednak interfejs API staje się bardziej skomplikowany, ograniczony ... i „brzydki” IMO. Ponadto nie zmusza to programisty do robienia właściwych rzeczy . Usuwa zdolność programisty do robienia złych rzeczy!źródło
DefaultResource
iFinalizableResource
która rozciąga się pierwszy i dodaje finalizatora. W produkcji używasz,DefaultResource
który nie ma finalizatora-> bez kosztów ogólnych. Podczas programowania i testowania konfigurujesz aplikację,FinalizableResource
która ma używać finalizatora, a zatem dodaje koszty ogólne. Podczas tworzenia i testowania nie stanowi to problemu, ale może pomóc zidentyfikować wycieki i naprawić je przed osiągnięciem produkcji. Jednak jeśli wyciek osiągnie PRD, możesz skonfigurować fabrykę, aby utworzyć X%,FinalizableResource
abyWydaje się, że jest to przypadek użycia
AutoCloseable
interfejsu itry-with-resources
instrukcja wprowadzona w Javie 7. Jeśli masz coś takiegowtedy Twoi konsumenci mogą go używać jako
i nie musisz się martwić wywołaniem jawnego,
release
niezależnie od tego, czy jego kod zgłasza wyjątek.Dodatkowa odpowiedź
Jeśli naprawdę szukasz, aby nikt nie mógł zapomnieć o korzystaniu z tej funkcji, musisz zrobić kilka rzeczy
MyService
są prywatne.MyService
który zapewni, że zostanie on później wyczyszczony.Zrobiłbyś coś takiego
Użyłbyś go jako
Początkowo nie polecałem tej ścieżki, ponieważ jest obrzydliwa bez lambd Java-8 i interfejsów funkcjonalnych (gdzie
MyServiceConsumer
sięConsumer<MyService>
pojawia) i jest dość imponująca dla twoich konsumentów. Ale jeśli potrzebujesz tylko tego,release()
aby zadzwonić, zadziała.źródło
try-with-resources
a nie tylko omijaćtry
, a tym samym przegapićclose
połączenie?try-with-resources
?.close()
jest wywoływany, gdy klasa się implementujeAutoCloseable
.using
blokiem deterministycznej finalizacji? Przynajmniej w języku C # staje się to dość wyraźnym wzorcem dla deweloperów, którzy mają zwyczaj korzystania z zasobów, które wymagają deterministycznej finalizacji. Coś łatwego i nawyk jest kolejną najlepszą rzeczą, gdy nie możesz zmusić kogoś do zrobienia czegoś. stackoverflow.com/a/2016311/84206tak, możesz
Możesz absolutnie zmusić ich do wydania i sprawić, że będzie w 100% niemożliwe do zapomnienia, uniemożliwiając im tworzenie nowych
Service
instancji.Jeśli zrobili coś takiego wcześniej:
Następnie zmień go, aby mieli do niego dostęp w ten sposób.
Ostrzeżenia:
AutoCloseable
interfejs, o którym ktoś wspomniał; ale podany przykład powinien zadziałać od razu po wyjęciu z pudełka i poza elegancją (która sama w sobie jest przyjemnym celem) nie powinno być żadnego ważnego powodu, aby to zmienić. Zauważ, że zamierza to powiedzieć, że możesz użyćAutoCloseable
w swojej prywatnej implementacji; zaletą mojego rozwiązania nad korzystaniem z niego przez użytkownikówAutoCloseable
jest to, że znowu nie można go zapomnieć.new Service
wywołania.Service
) należy do osoby dzwoniącej, czy nie, jest poza zakresem tej odpowiedzi. Ale jeśli zdecydujesz, że jest to absolutnie potrzebne, możesz to zrobić w ten sposób.źródło
Możesz spróbować użyć wzorca poleceń .
Daje to pełną kontrolę nad pozyskiwaniem i uwalnianiem zasobów. Dzwoniący przekazuje tylko zadania do Twojej Usługi, a Ty sam jesteś odpowiedzialny za pozyskiwanie i czyszczenie zasobów.
Jest jednak pewien haczyk. Dzwoniący nadal może to zrobić:
Ale możesz rozwiązać ten problem, gdy się pojawi. Gdy wystąpią problemy z wydajnością i okaże się, że niektórzy dzwoniący to robią, po prostu pokaż im właściwy sposób i rozwiąż problem:
Możesz to dowolnie skomplikować, wdrażając obietnice dla zadań zależnych od innych zadań, ale późniejsze wydawanie rzeczy również staje się bardziej skomplikowane. To tylko punkt wyjścia.
Asynchronizacja
Jak wskazano w komentarzach, powyższe tak naprawdę nie działa z żądaniami asynchronicznymi. To jest poprawne. Ale można to łatwo rozwiązać w Javie 8 za pomocą CompleteableFuture , szczególnie w
CompleteableFuture#supplyAsync
celu tworzenia indywidualnych przyszłości iCompleteableFuture#allOf
zapewnienia uwolnienia zasobów po zakończeniu wszystkich zadań. Alternatywnie, zawsze można użyć Threads lub ExecutorServices do opracowania własnej implementacji kontraktów futures / obietnic.źródło
Jeśli możesz, nie zezwalaj użytkownikowi na tworzenie zasobu. Pozwól użytkownikowi przekazać obiekt gościa do metody, która utworzy zasób, przekaże go do metody obiektu gościa, a następnie zwolni.
źródło
AutoCloseable
robi bardzo podobne rzeczy za kulisami. Pod innym kątem jest podobny do wstrzykiwania zależności .Mój pomysł był podobny do Polygnome.
Możesz mieć metody „do_something ()” w swojej klasie, po prostu dodając polecenia do prywatnej listy poleceń. Następnie zastosuj metodę „commit ()”, która faktycznie działa, i wywołuje „release ()”.
Jeśli więc użytkownik nigdy nie wywoła metody commit (), praca nigdy nie zostanie wykonana. Jeśli tak, zasoby zostaną uwolnione.
Następnie możesz użyć go jak foo.doSomething.doSomethineElse (). Commit ();
źródło
Inną alternatywą dla tych wymienionych byłoby użycie „inteligentnych referencji” do zarządzania enkapsulowanymi zasobami w sposób, który umożliwia automatycznemu czyszczeniu śmieci bez ręcznego zwalniania zasobów. Ich przydatność zależy oczywiście od wdrożenia. Przeczytaj więcej na ten temat w tym cudownym poście na blogu: https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
źródło
W przypadku każdego innego języka zorientowanego na klasy umieściłbym go w destruktorze, ale ponieważ nie jest to opcja, myślę, że można zastąpić metodę finalizacji. W ten sposób, gdy instancja wyłączy się z zakresu i zostanie wyrzucona, zostaną zwolnione.
Nie znam się zbytnio na Javie, ale myślę, że jest to cel, dla którego przeznaczona jest finalize ().
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize ()
źródło
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.