Rumah > Java > javaTutorial > teks badan

Java Cleaners: Cara Moden untuk Mengurus Sumber Luaran

PHPz
Lepaskan: 2024-08-14 12:40:32
asal
559 orang telah melayarinya

Kod untuk artikel ini boleh didapati di GitHub.
Jika anda jenis pengaturcara yang suka memahami dalaman cara sesuatu berfungsi sebelum melihat contoh,
anda boleh melompat terus ke Pembersih di belakang tabir selepas pengenalan.

  • Pengenalan
  • Pembersih Mudah dalam tindakan
  • Pencuci, cara yang betul
  • Pencuci, cara yang berkesan
  • Pembersih di belakang tabir

pengenalan

Fikirkan senario di mana anda mempunyai objek yang memegang rujukan kepada sumber luaran (fail, soket dan sebagainya). Dan anda ingin mempunyai kawalan ke atas cara sumber ini dikeluarkan apabila objek pegangan tidak lagi aktif/boleh diakses, bagaimana anda mencapainya di Java?. Sebelum Java 9 pengaturcara boleh menggunakan finalizer dengan mengatasi kaedah finalize() kelas Object. Penyelesai mempunyai banyak kelemahan, termasuk lambat, tidak boleh dipercayai dan berbahaya. Ia adalah salah satu ciri yang dibenci oleh mereka yang melaksanakan JDK dan mereka yang menggunakannya.

Sejak Java 9, Penyelesai telah ditamatkan dan pengaturcara mempunyai pilihan yang lebih baik untuk mencapai perkara ini dalam Pembersih, Pembersih menyediakan cara yang lebih baik untuk mengurus dan mengendalikan tindakan pembersihan/pemuktamadkan. Pembersih berfungsi dalam corak di mana mereka membenarkan objek pemegang sumber mendaftarkan diri mereka dan tindakan pembersihan yang sepadan. Dan kemudian Cleaners akan memanggil tindakan pembersihan apabila objek ini tidak boleh diakses oleh kod aplikasi.
Ini bukan artikel untuk memberitahu anda mengapa Cleaners lebih baik daripada Finalizers, walaupun saya akan menyenaraikan secara ringkas beberapa perbezaan mereka.

Penyudah Vs Pembersih

Penyudah Pembersih Pemakhir digunakan oleh salah satu utas Pemungut Sampah, anda sebagai pengaturcara tidak mempunyai kawalan ke atas utas yang akan menggunakan logik pemuktamadkan anda Tidak seperti pemuktamad, dengan Pembersih, pengaturcara boleh memilih untuk mengawal urutan yang menggunakan logik pembersihan. Logik penamat digunakan apabila objek sebenarnya sedang dikumpulkan oleh GC Logik pembersihan digunakan apabila objek menjadi
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, iaitu aplikasi kami tidak mempunyai cara untuk mengaksesnya lagi Memuktamadkan logik ialah sebahagian daripada objek yang memegang sumber Logik pembersihan dan keadaannya terkandung dalam objek yang berasingan. Tiada mekanisme pendaftaran/penyahdaftaran Menyediakan cara untuk mendaftarkan tindakan pembersihan dan permohonan/pembatalan pendaftaran yang jelas

Pembersih Mudah dalam tindakan

Cukup sembang-sembang biar kami lihat Cleaners beraksi.

Pemegang Sumber

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();
        }}
Salin selepas log masuk

Beberapa baris kod tetapi banyak yang berlaku di sini, Mari kita pecahkan

  1. PEMBERSIH yang berterusan adalah daripada jenis java.lang.ref.Cleaner, seperti yang anda boleh ketahui daripada namanya, ini ialah pusat dan titik permulaan bagi ciri Pembersih di Jawa. Pembolehubah CLEANER diisytiharkan sebagai statik seperti yang sepatutnya, Pembersih tidak boleh menjadi pembolehubah contoh, mereka harus dikongsi merentasi kelas yang berbeza sebanyak mungkin.
  2. Dalam pembina, contoh ResourceHolder mendaftarkan diri mereka kepada Cleaner bersama-sama dengan tindakan pembersihan mereka, tindakan pembersihan ialah kerja Runnable yang dijamin oleh Cleaner untuk menggunakan paling banyak sekali (paling banyak sekali, bermakna mungkin untuk tidak dijalankan sama sekali). Dengan memanggil kaedah daftar() Cleaner, keadaan ini pada dasarnya mengatakan dua perkara kepada Cleaner
    • Jejaki saya selagi saya hidup
    • Dan apabila saya tidak lagi aktif (Phantom Reachable), sila lakukan yang terbaik dan lakukan tindakan pembersihan saya.
  3. Dalam kaedah utama kami membuat instantiat objek ResourceHolder dan segera menetapkan pembolehubahnya kepada null, kerana objek itu hanya mempunyai satu rujukan pembolehubah, aplikasi kami tidak lagi boleh mengakses objek, iaitu, ia telah menjadi Phantom Boleh Dicapai
  4. Kami memanggil System.gc() untuk meminta JVM menjalankan Pengumpul Sampah, akibatnya ini akan mencetuskan Cleaner untuk menjalankan tindakan pembersihan. Biasanya, anda tidak perlu memanggil System.gc() tetapi semudah aplikasi kami, kita perlu memudahkan Cleaner menjalankan tindakan

Jalankan aplikasi dan semoga anda melihat saya sedang melakukan pembersihan di suatu tempat dalam output standard anda.

? AWAS
Kami bermula dengan cara yang paling mudah untuk menggunakan Pembersih, jadi kami boleh menunjukkan penggunaannya dengan cara yang mudah, perlu diingat walaupun ini tidak berkesan mahupun cara yang betul untuk menggunakan Pembersih

Pembersih, cara yang betul

Contoh pertama kami adalah lebih daripada cukup baik untuk melihat Pembersih beraksi,
tetapi seperti yang kami amaran, ini bukanlah cara yang betul untuk menggunakan Pembersih dalam aplikasi sebenar.
Mari lihat apa yang salah dengan apa yang kita lakukan.

  1. Kami memulakan objek Cleaner sebagai ahli kelas ResourceHolder: Seperti yang telah kami nyatakan sebelum ini, Cleaner harus dikongsi merentas Kelas dan tidak seharusnya tergolong dalam kelas individu, sebab setiap instance Cleaner mengekalkan urutan, yang merupakan sumber asli yang terhad , dan anda ingin berhati-hati apabila anda menggunakan sumber asli.
    Dalam aplikasi sebenar, kami biasanya mendapat objek Cleaner daripada utiliti atau kelas Singleton seperti

      private static CLEANER = AppUtil.getCleaner();
    
    Salin selepas log masuk
  2. Kami lulus dalam lambda sebagai tindakan Pembersihan kami: Anda seharusnya JANGAN SEKALI-KALI lulus dalam lambda sebagai tindakan pembersihan anda.
    Untuk memahami sebabnya,
    mari kita memfaktorkan semula contoh kita yang terdahulu dengan mengekstrak mesej yang dicetak dan menjadikannya pembolehubah contoh

    Pemegang Sumber

    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));
       }
    }
    
    Salin selepas log masuk

    Jalankan aplikasi dan lihat apa yang berlaku.
    Saya akan memberitahu anda apa yang berlaku,
    tindakan pembersihan tidak akan digunakan tidak kira berapa kali anda menjalankan aplikasi anda.
    Mari kita lihat mengapa

    • Secara dalaman, Cleaners menggunakan PhantomReference dan ReferenceQueue untuk menjejaki objek berdaftar, sebaik sahaja objek menjadi Phantom Reachable ReferenceQueue akan memberitahu Cleaner dan Pembersih akan menggunakan benangnya untuk menjalankan tindakan pembersihan yang sepadan.
    • Dengan mempunyai lambda mengakses ahli instance, kami memaksa lambda untuk memegang rujukan ini (contoh ResourceHolder), kerana ini objek tidak akan pernah menjadi Phantom Reachable kerana kod Aplikasi kami masih mempunyai rujukan kepadanya.

    ? NOTA
    Jika anda masih tertanya-tanya bagaimana dalam contoh pertama kami, tindakan pembersihan digunakan walaupun mempunyainya sebagai lambda. Sebabnya, lambda dalam contoh pertama tidak mengakses sebarang pembolehubah contoh dan tidak seperti kelas dalaman, Lambdas tidak akan secara tersirat memegang rujukan objek yang mengandungi melainkan mereka terpaksa melakukannya.

    Cara yang betul ialah merangkum tindakan pembersihan anda bersama-sama keadaan yang diperlukan dalam kelas bersarang statik.

    ? Amaran
    Jangan gunakan kelas dalam tanpa nama atau tidak, ini lebih teruk daripada menggunakan lambda kerana tika kelas dalam akan memegang rujukan kepada tika kelas luar tidak kira sama ada mereka mengakses pembolehubah tika mereka atau tidak.

  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();
    }
}

Salin selepas log masuk

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
    }
}


Salin selepas log masuk

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();
    
    Salin selepas log masuk

    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;
    
    Salin selepas log masuk
    Salin selepas log masuk

    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);
    
    Salin selepas log masuk

    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;
    
    Salin selepas log masuk
    Salin selepas log masuk

    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()
    
    Salin selepas log masuk

    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

    Atas ialah kandungan terperinci Java Cleaners: Cara Moden untuk Mengurus Sumber Luaran. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!