Project Loom: Java virtual threads sudah dekat

Beberapa hari yang lalu, Ron Pressler melahirkan sebuah artikel di State of Loom , yang hanya tidak bisa ditemukan oleh javist yang paling malas. Artikel ini benar-benar bagus, ada banyak metafora menarik di dalamnya, yang akan saya gunakan sekarang dengan tidak hati-hati tanpa referensi ke sumbernya.

Untuk bagian saya, saya secara tidak sengaja mengizinkan saya untuk mengekspresikan beberapa skeptisisme, tetapi ketika dengan Proyek Loom ini akhirnya dapat benar-benar dikerjakan. Secara harfiah satu jam kemudian, balasan datang dari Ron sendiri - β€œdan kamu coba!” . Yah, aku harus mencobanya.

Apa yang diperlukan untuk percobaan:


Untuk mulai dengan, saya memutuskan untuk melihat seperti apa contoh klasik dengan aliran dari dokumentasi untuk Kotlin Coroutine akan terlihat seperti . Contohnya ditulis di Kotlin, tetapi menulis ulang di Jawa tidak sulit:

public class Main {
    public static void main(String[] args) {
        var c = new AtomicLong();
        for (var i = 0; i < 1_000_000; i++) {
            new Thread(() -> {
                c.incrementAndGet();
            }).start();
        }

        System.out.println(c.get());
    }
}

Kami mulai dan memastikan bahwa contoh masih hang, seperti sebelumnya:

javac Main.java && java Main

Sekarang tulis ulang contoh menggunakan utas virtual yang disediakan Project Loom kepada kami:

for (var i = 0; i < 1_000_000; i++) {
    Thread.startVirtualThread(() -> {
        c.incrementAndGet();
    });
}

Hasilnya tidak lama datang:

1000000

Berapa lama?
, .
-, Gil Tene, β€” .
-, Project Loom, . , β€” .

Tetapi dalam dirinya sendiri itu tidak membuat saya terkesan terlalu banyak. Pada akhirnya, dengan coroutine di Kotlin, hasil yang sama mudah dicapai.

Ron dalam artikelnya dengan benar mengamati bahwa Kotlin harus memperkenalkan fungsi delay (), yang memungkinkan coroutine untuk β€œtertidur”, karena Thread.sleep () tidak mengirim coroutine saat ini untuk tidur, tetapi thread scheduler saat ini, yang tidak banyak, biasanya berdasarkan kuantitas CPU

Bagaimana dengan Project Loom?

for (var i = 0; i < 1_000_000; i++) {
  Thread.startVirtualThread(() -> {
    c.incrementAndGet();
    try {
        Thread.sleep(1_000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
  });
}

Hasil:

- 400K

Tapi ini sudah menarik! Dengan Project Loom, panggilan ke Thread.sleep () dapat membedakan apakah itu dalam utas biasa atau dalam utas virtual, dan kerjanya berbeda.

Ini sendiri sangat keren. Tapi mari kita gali lebih dalam:

var threads = new ArrayList<Thread>();
var cores = 10;
for (var i = 0; i < cores; i++) {
    var t = Thread.startVirtualThread(() -> {
        var bestUUID = "";
        for (var j = 0; j < 1_000_000; j++) {
            var currentUUID = UUID.randomUUID().toString();
            if (currentUUID.compareTo(bestUUID) > 0) {
                bestUUID = currentUUID;
            }
        }
        System.out.println("Best slow UUID is " + bestUUID);
    });
    threads.add(t);
}

for (var i = 0; i < cores; i++) {
    var t = Thread.startVirtualThread(() -> {
        var bestUUID = UUID.randomUUID().toString();
        System.out.println("Best fast UUID is " + bestUUID);
    });
    threads.add(t);
}

for (Thread t : threads) {
    t.join();
}

Di sini kita menjalankan 10 tugas lambat dan 10 tugas cepat. Tugas cepat lebih cepat daripada yang lambat satu juta kali, jadi akan logis untuk menganggap bahwa mereka menyelesaikan lebih awal.

Tapi itu tidak ada di sana:

Best slow UUID is fffffde4-8c70-4ce6-97af-6a1779c206e1
Best slow UUID is ffffe33b-f884-4206-8e00-75bd78f6d3bd
Best slow UUID is fffffeb8-e972-4d2e-a1f8-6ff8aa640b70
Best fast UUID is e13a226a-d335-4d4d-81f5-55ddde69e554
Best fast UUID is ec99ed73-23b8-4ab7-b2ff-7942442a13a9
Best fast UUID is c0cbc46d-4a50-433c-95e7-84876a338212
Best fast UUID is c7672507-351f-4968-8cd2-2f74c754485c
Best fast UUID is d5ae642c-51ce-4b47-95db-abb6965d21c2
Best fast UUID is f2f942e3-f475-42b9-8f38-93d89f978578
Best fast UUID is 469691ee-da9c-4886-b26e-dd009c8753b8
Best fast UUID is 0ceb9554-a7e1-4e37-b477-064c1362c76e
Best fast UUID is 1924119e-1b30-4be9-8093-d5302b0eec5f
Best fast UUID is 94fe1afc-60aa-43ce-a294-f70f3011a424
Best slow UUID is fffffc24-28c5-49ac-8e30-091f1f9b2caf
Best slow UUID is fffff303-8ec1-4767-8643-44051b8276ca
Best slow UUID is ffffefcb-614f-48e0-827d-5e7d4dea1467
Best slow UUID is fffffed1-4348-456c-bc1d-b83e37d953df
Best slow UUID is fffff6d6-6250-4dfd-8d8d-10425640cc5a
Best slow UUID is ffffef57-c3c3-46f5-8ac0-6fad83f9d4d6
Best slow UUID is fffff79f-63a6-4cfa-9381-ee8959a8323d


Intuisi hanya berfungsi selama jumlah tugas lambat kurang dari jumlah inti CPU Anda.

Alasannya sederhana - saat ini Project Loom menggunakan ForkJoinPool biasa. Akibatnya, terlepas dari kenyataan bahwa dokumentasi dan gagasan menunjukkan bahwa utas virtual "preemtive", saat ini mereka berperilaku "kooperatif". Seperti coroutine di Kotlin, sebenarnya.

Perlu dicatat bahwa dalam artikel yang disebutkan di atas, Ron menyebutkan bahwa dia juga merefleksikan perilaku preemtion paksa, seperti pada utas normal. Tetapi sejauh ini ini belum dilaksanakan, karena tidak sepenuhnya jelas bagaimana perilaku seperti itu berguna ketika bisa ada puluhan ribu utas. Namun, dalam Go 1.14, preemtion paksa diperkenalkan secara diam-diam .

Panggilan fungsi, tidak seperti Go, tidak menghasilkan saklar konteks. Penangguhan, seperti di Kotlin, juga tidak dikirimkan. Tetapi Anda dapat melakukannya dengan Thread.yield (), atau dengan memanggil fungsi Java IO apa saja: System.out.println (""), misalnya.

Perhitungannya sederhana - sebagian besar program nyata menggunakan pemblokiran IO cukup sering. Dan itu adalah penggunaan Project Loom yang berusaha untuk memecahkan di tempat pertama.

Beberapa kesimpulan:


Saya harus mengakui bahwa terlepas dari skeptisisme awal saya, Project Loom mengesankan saya secara positif. Untuk pengguna Java biasa, ini menjanjikan multithreading ringan tanpa perlu beralih ke bahasa lain, menggunakan perpustakaan atau kerangka kerja. Yang sudah terdengar bagus.

Tetapi revolusi utama, seperti yang saya perkirakan, proyek ini akan dibuat di antara pengembang perpustakaan yang masih harus memecahkan masalah konkurensi lagi, dan lagi, dan lagi. Sekarang, dengan distribusi JDK15, masalah ini dapat ditransfer ke JVM, karena mereka digunakan untuk mentransfer memori (GC) dan optimasi kode (JIT) ke JVM.

Tautan ke artikel asli saya jika Anda lebih suka membaca dalam bahasa Inggris.

All Articles