Heim > Java > javaLernprogramm > Hauptteil

Java Cleaner: Die moderne Art, externe Ressourcen zu verwalten

PHPz
Freigeben: 2024-08-14 12:40:32
Original
595 Leute haben es durchsucht

Code für diesen Artikel finden Sie auf GitHub.
Wenn Sie der Typ Programmierer sind, der gerne die Funktionsweise von Dingen versteht, bevor er sich Beispiele ansieht,
Sie können nach der Einführung direkt zu den Reinigungskräften hinter den Kulissen springen.

  • Einführung
  • Simple Cleaner in Aktion
  • Reinigung, richtig
  • Reiniger, der effektive Weg
  • Reinigungskräfte hinter den Kulissen

Einführung

Stellen Sie sich ein Szenario vor, in dem Sie über ein Objekt verfügen, das Verweise auf externe Ressourcen (Dateien, Sockets usw.) enthält. Und Sie möchten die Kontrolle darüber haben, wie diese Ressourcen freigegeben werden, sobald das Halteobjekt nicht mehr aktiv/zugänglich ist. Wie erreichen Sie das in Java? Vor Java 9 konnten Programmierer einen Finalizer verwenden, indem sie die finalize()-Methode der Objektklasse überschrieben. Finalizer haben viele Nachteile, unter anderem sind sie langsam, unzuverlässig und gefährlich. Es ist eine dieser Funktionen, die sowohl von denen, die das JDK implementieren, als auch von denen, die es verwenden, gehasst werden.

Seit Java 9 sind Finalizer veraltet und Programmierer haben mit Cleanern eine bessere Möglichkeit, dies zu erreichen. Cleaner bieten eine bessere Möglichkeit, Bereinigungs-/Finalisierungsaktionen zu verwalten und abzuwickeln. Reiniger arbeiten nach einem Muster, bei dem sie ressourcenhaltende Objekte sich selbst und ihre entsprechenden Reinigungsaktionen registrieren lassen. Und dann rufen Cleaners die Reinigungsaktionen auf, sobald diese Objekte für den Anwendungscode nicht zugänglich sind.
Dies ist nicht der Artikel, der Ihnen erklärt, warum Cleaner besser sind als Finalizer, obwohl ich einige ihrer Unterschiede kurz auflisten werde.

Finalizer vs. Reiniger

Finalisierer Reiniger Finalizer werden von einem der Garbage Collector-Threads aufgerufen. Sie als Programmierer haben keine Kontrolle darüber, welcher Thread Ihre Finalisierungslogik aufruft. Anders als bei Finalisierern können Programmierer bei Cleanern die Kontrolle über den Thread behalten, der die Bereinigungslogik aufruft. Abschlusslogik wird aufgerufen, wenn das Objekt tatsächlich von GC erfasst wird Die Reinigungslogik wird aufgerufen, wenn das Objekt
Finalizers Cleaners
Finalizers are invoked by one of Garbage Collector’s threads, you as a programmer don’t have control over what thread will invoke your finalizing logic Unlike with finalizers, with Cleaners, programmers can opt to have control over the thread that invokes the cleaning logic.
Finalizing logic is invoked when the object is actually being collected by GC Cleaning logic is invoked when the object becomes Phantom Reachable, that is our application has no means to access it anymore
Finalizing logic is part of the object holding the resources Cleaning logic and its state are encapsulated in a separate object.
No registration/deregistration mechanism Provides means for registering cleaning actions and explicit invocation/deregistration
Phantom Reachable wird, das heißt, unsere Anwendung hat keine Möglichkeit mehr, darauf zuzugreifen Finalisierungslogik ist Teil des Objekts, das die Ressourcen enthält Die Reinigungslogik und ihr Status sind in einem separaten Objekt gekapselt. Kein Registrierungs-/Abmeldemechanismus Bietet Mittel zum Registrieren von Reinigungsaktionen und zum expliziten Aufruf/Abmelden

Einfacher Reiniger in Aktion

Genug Geplauder, damit wir Reinigungskräfte in Aktion sehen können.

ResourceHolder

import java.lang.ref.Cleaner;

public class ResourceHolder {
 private static final Cleaner CLEANER = Cleaner.create();
        public ResourceHolder() {
            CLEANER.register(this, () -> System.out.println("I'm doing some clean up"));
        }
        public static void main(String... args) {
            ResourceHolder resourceHolder = new ResourceHolder();
            resourceHolder = null;
            System.gc();
        }}
Nach dem Login kopieren

Wenige Codezeilen, aber hier passiert viel. Lassen Sie es uns aufschlüsseln

  1. Die Konstante CLEANER ist vom Typ java.lang.ref.Cleaner, wie Sie am Namen erkennen können. Dies ist der zentrale und Ausgangspunkt der Cleaners-Funktion in Java. Die CLEANER-Variable wird als statisch deklariert, wie sie sein sollte. Cleaner sollten niemals Instanzvariablen sein. Sie sollten so weit wie möglich in verschiedenen Klassen geteilt werden.
  2. Im Konstruktor registrieren sich Instanzen von ResourceHolder zusammen mit ihrer Reinigungsaktion beim Cleaner. Die Reinigungsaktion ist ein ausführbarer Job, den der Cleaner garantiert höchstens einmal aufruft (höchstens einmal, was bedeutet, dass es möglich ist, ihn nicht auszuführen). überhaupt). Durch den Aufruf der Methode register() des Cleaners sagen diese Instanzen im Grunde zwei Dinge zum Cleaner
    • Behalte den Überblick über mich, solange ich lebe
    • Und sobald ich nicht mehr aktiv bin (Phantom Reachable), geben Sie bitte Ihr Bestes und rufen Sie meine Reinigungsaktion auf.
  3. In der Hauptmethode instanziieren wir ein Objekt von ResourceHolder und setzen seine Variable sofort auf Null. Da das Objekt nur eine Variablenreferenz hat, kann unsere Anwendung nicht mehr auf das Objekt zugreifen, d. h. es ist zu geworden Phantom erreichbar
  4. Wir rufen System.gc() auf, um JVM aufzufordern, den Garbage Collector auszuführen. Dies führt dazu, dass der Reiniger die Reinigungsaktion ausführt. Normalerweise müssen Sie System.gc() nicht aufrufen, aber so einfach wie unsere Anwendung: Wir müssen es dem Cleaner ermöglichen, die Aktion auszuführen

Führen Sie die Anwendung aus, und hoffentlich sehen Sie, dass ich irgendwo in Ihrer Standardausgabe etwas bereinige.

? VORSICHT
Wir haben mit der einfachsten möglichen Art der Verwendung von Reinigern begonnen, damit wir die Verwendung auf vereinfachte Weise demonstrieren können. Bedenken Sie jedoch, dass dies weder effektiv noch die richtige Art der Verwendung von Reinigern ist

Reiniger, der richtige Weg

Unser erstes Beispiel war mehr als gut genug, um Cleaners in Aktion zu sehen
aber wie wir gewarnt haben, ist es nicht die richtige Art und Weise, Reiniger in einer realen Anwendung einzusetzen.
Mal sehen, was an dem, was wir getan haben, falsch ist.

  1. Wir haben ein Cleaner-Objekt als Klassenmitglied des ResourceHolder initiiert: Wie bereits erwähnt, sollten Cleaner von allen Klassen gemeinsam genutzt werden und nicht zu einzelnen Klassen gehören. Der Grund dafür ist, dass jede Cleaner-Instanz einen Thread verwaltet, der eine begrenzte native Ressource darstellt , und Sie sollten vorsichtig sein, wenn Sie native Ressourcen verbrauchen.
    In einer realen Anwendung erhalten wir normalerweise ein Cleaner-Objekt von einem Dienstprogramm oder einer Singleton-Klasse wie

      private static CLEANER = AppUtil.getCleaner();
    
    Nach dem Login kopieren
  2. Wir haben ein Lambda als unsere Reinigungsaktion übergeben: Sie sollten NIEMALS ein Lambda als Ihre Reinigungsaktion übergeben.
    Um zu verstehen, warum,
    Lassen Sie uns unser vorheriges Beispiel umgestalten, indem wir die ausgedruckte Nachricht extrahieren und sie zu einer Instanzvariablen machen

    ResourceHolder

    public class ResourceHolder {
       private static final Cleaner CLEANER = Cleaner.create();
       private final String cleaningMessage = "I'm doing some clean up";
       public ResourceHolder() {
           CLEANER.register(this, () -> System.out.println(cleaningMessage));
       }
    }
    
    Nach dem Login kopieren

    Führen Sie die Anwendung aus und sehen Sie, was passiert.
    Ich werde dir sagen, was passiert,
    Die Reinigungsaktion wird nie aufgerufen, egal wie oft Sie Ihre Anwendung ausführen.
    Lassen Sie uns sehen, warum

    • Intern nutzen Cleaner PhantomReference und ReferenceQueue, um den Überblick über registrierte Objekte zu behalten. Sobald ein Objekt Phantom Reachable wird, benachrichtigt die ReferenceQueue den Cleaner und der Cleaner verwendet seinen Thread, um die entsprechende Reinigungsaktion auszuführen.
    • Indem das Lambda auf das Instanzmitglied zugreift, zwingen wir das Lambda, diese Referenz (der ResourceHolder-Instanz) zu halten. Aus diesem Grund wird das Objekt niemals Phantom Reachable<🎜 werden > weil unser Anwendungscode immer noch darauf verweist.

    ? HINWEIS
    Wenn Sie sich immer noch fragen, wie in unserem ersten Beispiel die Reinigungsaktion aufgerufen wird, obwohl sie als Lambda vorliegt. Der Grund dafür ist, dass das Lambda im ersten Beispiel nicht auf Instanzvariablen zugreift und im Gegensatz zu inneren Klassen die enthaltende Objektreferenz nicht implizit enthält, es sei denn, sie werden dazu gezwungen.
    Der richtige Weg besteht darin, Ihre Reinigungsaktion zusammen mit dem erforderlichen Zustand in einer statischen verschachtelten Klasse zu kapseln.


    ? Achtung
    Verwenden Sie die innere Klasse nicht anonym oder nicht, das ist schlimmer als die Verwendung von Lambda, da eine Instanz der inneren Klasse einen Verweis auf die Instanz der äußeren Klasse enthalten würde, unabhängig davon, ob sie auf ihre Instanzvariable zugreift oder nicht.

  3. We didn't make use of the return value from the Cleaner.create(): The create() actually returns something very important.a Cleanable object, this object has a clean() method that wraps your cleaning logic, you as a programmer can opt to do the cleanup yourself by invoking the clean() method. As mentioned earlier, another thing that makes Cleaners superior to Finalizers is that you can actually deregister your cleaning action. The clean() method actually deregisters your object first, and then it invokes your cleaning action, this way it guarantees the at-most once behavior.

Now let us improve each one of these points and revise our ResourceHolder class

ResourceHolder

import java.lang.ref.Cleaner;

public class ResourceHolder {

    private final Cleaner.Cleanable cleanable;
    private final ExternalResource externalResource;

    public ResourceHolder(ExternalResource externalResource) {
        cleanable = AppUtil.getCleaner().register(this, new CleaningAction(externalResource));
        this.externalResource = externalResource;
    }

//    You can call this method whenever is the right time to release resource
    public void releaseResource() {
        cleanable.clean();
    }

    public void doSomethingWithResource() {
        System.out.printf("Do something cool with the important resource: %s \n", this.externalResource);
    }

    static class CleaningAction implements Runnable {
        private ExternalResource externalResource;

        CleaningAction(ExternalResource externalResource) {
            this.externalResource = externalResource;
        }

        @Override
        public void run() {
//          Cleaning up the important resources
            System.out.println("Doing some cleaning logic here, releasing up very important resource");
            externalResource = null;
        }
    }

    public static void main(String... args) {
        ResourceHolder resourceHolder = new ResourceHolder(new ExternalResource());
        resourceHolder.doSomethingWithResource();
/*
        After doing some important work, we can explicitly release
        resources/invoke the cleaning action
*/
        resourceHolder.releaseResource();
//      What if we explicitly invoke the cleaning action twice?
        resourceHolder.releaseResource();
    }
}

Nach dem Login kopieren

ExternalResource is our hypothetical resource that we want to release when we’re done with it.
The cleaning action is now encapsulated in its own class, and we make use of the CleaniangAction object, we call it’s clean() method in the releaseResources() method to do the cleanup ourselves.
As stated earlier, Cleaners guarantee at most one invocation of the cleaning action, and since we call the clean() method explicitly the Cleaner will not invoke our cleaning action except in the case of a failure like an exception is thrown before the clean method is called, in this case the Cleaner will invoke our cleaning action when the ResourceHolder object becomes Phantom Reachable, that is we use the Cleaner as our safety-net, our backup plan when the first plan to clean our own mess doesn’t work.

❗ IMPORTANT
The behavior of Cleaners during System.exit is implementation-specific. With this in mind, programmers should always prefer to explicitly invoke the cleaning action over relying on the Cleaners themselves..

Cleaners, the effective way

By now we’ve established the right way to use Cleaners is to explicitly call the cleaning action and rely on them as our backup plan.What if there’s a better way? Where we don’t explicitly call the cleaning action, and the Cleaner stays intact as our safety-net.
This can be achieved by having the ResourceHolder class implement the AutoCloseable interface and place the cleaning action call in the close() method, our ResourceHolder can now be used in a try-with-resources block. The revised ResourceHolder should look like below.

ResourceHolder

import java.lang.ref.Cleaner.Cleanable;

public class ResourceHolder implements AutoCloseable {

    private final ExternalResource externalResource;

    private final Cleaner.Cleanable cleanable;

    public ResourceHolder(ExternalResource externalResource) {
        this.externalResource = externalResource;
        cleanable = AppUtil.getCleaner().register(this, new CleaningAction(externalResource));
    }

    public void doSomethingWithResource() {
        System.out.printf("Do something cool with the important resource: %s \n", this.externalResource);
    }
    @Override
    public void close() {
        System.out.println("cleaning action invoked by the close method");
        cleanable.clean();
    }

    static class CleaningAction implements Runnable {
        private ExternalResource externalResource;

        CleaningAction(ExternalResource externalResource) {
            this.externalResource = externalResource;
        }

        @Override
        public void run() {
//            cleaning up the important resources
            System.out.println("Doing some cleaning logic here, releasing up very important resources");
            externalResource = null;
        }
    }

    public static void main(String[] args) {
//      This is an effective way to use cleaners with instances that hold crucial resources
        try (ResourceHolder resourceHolder = new ResourceHolder(new ExternalResource(1))) {
            resourceHolder.doSomethingWithResource();
            System.out.println("Goodbye");
        }
/*
    In case the client code does not use the try-with-resource as expected,
    the Cleaner will act as the safety-net
*/
        ResourceHolder resourceHolder = new ResourceHolder(new ExternalResource(2));
        resourceHolder.doSomethingWithResource();
        resourceHolder = null;
        System.gc(); // to facilitate the running of the cleaning action
    }
}


Nach dem Login kopieren

Cleaners behind the scene

? NOTE
To understand more and see how Cleaners work, checkout the OurCleaner class under the our_cleaner package that imitates the JDK real implementation of Cleaner. You can replace the real Cleaner and Cleanable with OurCleaner and OurCleanable respectively in all of our examples and play with it.

Let us first get a clearer picture of a few, already mentioned terms, phantom-reachable, PhantomReference and ReferenceQueue

  • Consider the following code

    Object myObject = new Object();
    
    Nach dem Login kopieren

    In the Garbage Collector (GC) world the created instance of Object is said to be strongly-reachable, why? Because it is alive, and in-use i.e., Our application code has a reference to it that is stored in the myObject variable, assume we don’t set another variable and somewhere in our code this happens

    myObject = null;
    
    Nach dem Login kopieren
    Nach dem Login kopieren

    The instance is now said to be unreachable, and is eligible for reclamation by the GC.
    Now let us tweak the code a bit

    Object myObject = new Object();
    PhantomReference<Object> reference = new PhantomReference<>(myObject, null);
    
    Nach dem Login kopieren

    Reference is a class provided by JDK to represent reachability of an object during JVM runtime, the object a Reference object is referring to is known as referent, PhantomReference is a type(also an extension) of Reference whose purpose will be explained below in conjunction with ReferenceQueue.
    Ignore the second parameter of the constructor for now, and again assume somewhere in our code this happens again

    myObject = null;
    
    Nach dem Login kopieren
    Nach dem Login kopieren

    Now our object is not just unreachable it is phantom-reachable because no part of our application code can access it, AND it is a referent of a PhantomReference object.

  • After the GC has finalized a phantom-reachable object, the GC attaches its PhantomReference object(not the referent) to a special kind of queue called ReferenceQueue. Let us see how these two concepts work together

    Object myObject = new Object();
    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    PhantomReference<Object> reference1 = new PhantomReference<>(myObject, queue);
    myObject = null;
    PhantomReference<Object> reference2 = (PhantomReference)queue.remove()
    
    Nach dem Login kopieren

    We supply a ReferenceQueue when we create a PhantomReference object so the GC knows where to attach it when its referent has been finalized. The ReferenceQueue class provides two methods to poll the queue, remove(), this will block when the queue is empty until the queue has an element to return, and poll() this is non-blocking, when the queue is empty it will return null immediately.
    With that explanation, the code above should be easy to understand, once myObject becomes phantom-reachable the GC will attach the PhantomReference object to queue and we get it by using the remove() method, that is to say reference1 and reference2 variables refer to the same object.

  • Now that these concepts are out of the way, let’s explain two Cleaner-specific types

    1. For each cleaning action, Cleaner will wrap it in a Cleanable instance, Cleanable has one method, clean(), this method ensure the at-most once invocation behavior before invoking the cleaning action.
    2. PhantomCleanable implements Cleanable and extends PhantomReference, this class is the Cleaner’s way to associate the referent(resource holder) with their cleaning action

    From this point on understanding the internals of Cleaner should be straight forward.

    Cleaner Life-Cycle Overview

    Java Cleaners: The Modern Way to Manage External Resources

    Let us look at the life-cycle of a Cleaner object

    • The static Cleaner.create() method instantiates a new Cleaner but it also does a few other things

      • It instantiates a new ReferenceQueue, that the Cleaner objet’s thread will be polling
      • It creates a doubly linked list of PhantomCleanable objects, these objects are associated with the queue created from the previous step.
      • It creates a PhantomCleanable object with itself as the referent and empty cleaning action.
      • It starts a daemon thread that will be polling the ReferenceQueue as long as the doubly linked list is not empty.

      By adding itself into the list, the cleaner ensures that its thread runs at least until the cleaner itself becomes unreachable

    • For each Cleaner.register() call, the cleaner creates an instance of PhantomCleanable with the resource holder as the referent and the cleaning action will be wrapped in the clean() method, the object is then added to the aforementioned linked list.

    • The Cleaner’s thread will be polling the queue, and when a PhantomCleanable is returned by the queue, it will invoke its clean() method. Remember the clean() method only calls the cleaning action if it manages to remove the PhantomCleanable object from the linked list, if the PhantomCleanable object is not on the linked list it does nothing

    • The thread will continue to run as long as the linked list is not empty, this will only happen when

      • All the cleaning actions have been invoked, and
      • The Cleaner itself has become phantom-reachable and has been reclaimed by the GC

    Das obige ist der detaillierte Inhalt vonJava Cleaner: Die moderne Art, externe Ressourcen zu verwalten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage