CodeGym /Kurslar /JAVA 25 SELF /Virtual thread-lərlə Executor-un istifadəsi

Virtual thread-lərlə Executor-un istifadəsi

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Qısa olaraq əsas olan

Artıq java.util.concurrent paketindəki siniflərlə, xüsusən də ExecutorService ilə bir az tanışsınız. Bu, sanki “tapşırıq meneceri”dir: siz ona işi göndərirsiniz (məsələn, submit() vasitəsilə), o isə hansı thread-lə və nə vaxt icra olunacağına özü qərar verir. Adətən “pərdə arxasında” sabit ölçülü thread pool işləyir, bu da resurslara qənaət edir və hər tapşırıq üçün yeni thread yaratmır.

Lakin virtual thread-lərlə hər şey dəyişir! İndi özünüzə hər tapşırığa — ayrıca thread “lüksünü” rəva görə bilərsiniz və JVM-in resurslarının tükənməsindən qorxmazsınız.

Yeni üsul: Executors.newVirtualThreadPerTaskExecutor()

Java 21-də hər tapşırığı ayrıca virtual thread-də işə salan ExecutorService yaratmağın yeni yolu peyda oldu:

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

Vacib fərq:

  • Köhnə thread pool-lar (Executors.newFixedThreadPool, Executors.newCachedThreadPool) OS thread-lərinin “bahalığı” səbəbindən eyni vaxtda icra olunan tapşırıqların sayını məhdudlaşdırırdı.
  • Yeni virtual Executor demək olar məhdudiyyətsizdir: hər tapşırıq üçün öz yüngül virtual thread.

Sadə nümunə

10 tapşırığı virtual Executor-a göndərək:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualExecutorDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

        for (int i = 1; i <= 10; i++) {
            int taskId = i; // lambda üçün dəyişəni tuturuq
            executor.submit(() -> {
                System.out.println("Task " + taskId + " is running in thread: " +
                        Thread.currentThread());
            });
        }

        executor.shutdown();
    }
}

Nə baş verir?
Hər tapşırıq öz virtual thread-ində işə düşəcək və siz bu kimi sətirlər görəcəksiniz:

Task 1 is running in thread: VirtualThread[#24]/runnable@ForkJoinPool-1-worker-1
...

2. Kütləvi paralellik: minlərlə tapşırıq — problem deyil!

Virtual thread-lərin gücünü hiss etmək üçün ExecutorService10 yox, məsələn, 100_000 tapşırıq göndərək. Klassik pool-larda bu, fili soyuducuya yerləşdirməyə bənzəyərdi: JVM tez yaddaşı tükədər və ya ciddi şəkildə ləngiyərdi. Virtual thread-lərlə isə hər şey fərqlidir!

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualExecutorMassiveDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

        for (int i = 1; i <= 100_000; i++) {
            int taskId = i;
            executor.submit(() -> {
                // Nümunə üçün — sadəcə 1 ms yatırıq
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                // System.out.println("Task " + taskId + " done."); // Çap etmirik, əks halda çox sətir olacaq!
            });
        }

        executor.shutdown();
    }
}

Diqqət: ekrana 100_000 sətir çıxarmaq pis fikirdir: konsol virtual thread-lərdən daha tez “tıxanacaq”. Yaxşısı budur ki, ya ümumiyyətlə konsola yazmayın, ya da yalnız ilk bir neçə tapşırığı göstərin.

3. newVirtualThreadPerTaskExecutor necə işləyir

Qısa desək: bu ExecutorService siz ona göndərdiyiniz hər tapşırıq üçün yeni virtual thread yaradır. Sabit ölçülü pool-dan fərqli olaraq, burada tapşırıq növbəsi və eyni vaxtda işləyən thread-lərin sərt məhdudiyyəti yoxdur (JVM və avadanlıq limitləriniz istisna olmaqla).

Arxitektura baxımından:

  • Virtual thread-lər azsaylı real OS (carrier) thread-lərinə “map olunur”.
  • JVM hansı virtual thread-in nə vaxt işə düşəcəyinə, dayandırılıb bərpa olunacağına özü qərar verir.
  • Əgər thread bloklanırsa (məsələn, fayl oxumaqda və ya şəbəkə gözləməsində), JVM virtual thread-i “dondurub” carrier-thread-i digər tapşırıqlar üçün azad edə bilər.

4. Nümunə: nəticələrin Future ilə işlənməsi

Əgər tapşırıq nəticə qaytarırsa, ExecutorService Future tipində obyekt qaytarır. Hər şey adi thread-lərdəki kimidir:

import java.util.concurrent.*;

public class VirtualExecutorWithResult {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

        Future<String> future = executor.submit(() -> {
            Thread.sleep(500);
            return "Hello from virtual thread!";
        });

        System.out.println("Result: " + future.get()); // Nəticəni gözləyirik

        executor.shutdown();
    }
}

Hər şey tanışdır: dəyər qaytaran tapşırıqları göndərə, nəticəni get() ilə gözləyə bilərsiniz, istisnalar isə standart qaydada işlənir.

5. Executor-u düzgün necə bağlamaq

Proqramın “asılı” qalmaması üçün ExecutorService-i bağlamağı unutmaq olmaz (thread-lər virtual olsa belə).

shutdown()awaitTermination

executor.shutdown(); // Deyirik: artıq tapşırıq qəbul etmirik
executor.awaitTermination(1, TimeUnit.MINUTES); // Bütün tapşırıqların bitməsini gözləyirik (maksimum 1 dəqiqə)

Bu niyə vacibdir?
Əgər shutdown() çağırılmasa, virtual thread-lər yaşamağa davam edə bilər və main() bitdikdən sonra belə proqram bağlanmaya bilər. Bu, yeni başlayanların tipik səhvidir.

6. Faydalı nüanslar

Müqayisə: virtual Executor vs klassik thread pool

Klassik pool (newFixedThreadPool) Virtual Executor (newVirtualThreadPerTaskExecutor)
Thread-lərin sayı Pool-un ölçüsü ilə məhdud Hər tapşırığa bir virtual thread, demək olar məhdudiyyətsiz
Növbədə tapşırıqlar Bəli, bütün thread-lər məşğul olduqda Adətən yoxdur: tapşırıq dərhal thread alır
Thread-in dəyəri Yüksək (stack, OS resursları) Çox aşağı (planlaşdırma JVM tərəfindən)
Miqyaslana bilmə Məhduddur Demək olar məhdud deyil
Nələr üçün uyğundur CPU-bound tapşırıqlar, məhdud paralellik I/O-bound tapşırıqlar, kütləvi paralellizm

Veb serverlərlə inteqrasiya

Müasir veb serverlər (məsələn, Tomcat, Jetty, Undertow) artıq virtual thread-ləri dəstəkləməyə başlayır. Bu o deməkdir ki, hər HTTP sorğunu ayrı virtual thread-də emal etmək olar və istifadəçi axını zamanı “boğulmaqdan” qorxmazsınız.

Üstünlük: callback-lər və CompletableFuture ilə mürəkkəb asinxron sxemlər qurmağa ehtiyac yoxdur; kod sadələşir — adi bloklayan kod yaza bilərsiniz, lakin tətbiq yenə də miqyaslanır.

Kütləvi test və yükün imitasiya edilməsi

Virtual thread-lər minlərlə eyni vaxtlı istifadəçi, sorğu və ya əməliyyatı “simulyasiya” etmək lazım olan testlər üçün əladır. Məsələn, serverə 10_000 paralel sorğu göndərən test, hər biri öz virtual thread-ində.

Faylların və şəbəkə bağlantılarının paralel emalı

Əgər tətbiq çoxlu fayl və ya şəbəkə bağlantısı ilə işləyirsə, hər bağlantını ayrıca virtual thread-də emal etmək olar, pool-ları əl ilə idarə etməyə ehtiyac olmadan.

7. Virtual Executor-larla işləyərkən tipik səhvlər

Xəta №1: shutdown() çağırmağı unutmaq. Əgər Executor-u bağlamasanız, proqram tamamlanmayacaq — virtual thread-lər hələ də yeni tapşırıqları gözləyəcəklər. Lazım olduqda awaitTermination(...) əlavə edin.

Xəta №2: ağır hesablamalar üçün virtual thread-lərdən istifadə. Virtual thread-lər CPU-ya tam yük verən tapşırıqları sürətləndirmir. CPU-bound üçün sabit pool-u (Executors.newFixedThreadPool) istifadə etmək və ölçünü diqqətlə seçmək daha yaxşıdır.

Xəta №3: tapşırıq daxilində istisnaları görməməzlikdən gəlmək. Əgər tapşırıq istisna atsa, o, əsas thread-ə düşməyəcək — Future vasitəsilə ( get() metodu) və ya lambda daxilində try/catch ilə işləyin.

Xəta №4: köhnə və yeni sintaksisi/JDK versiyasını qarışdırmaq. Düzgün JDK versiyasından (Java 21+) istifadə etdiyinizi və IDE-nin virtual thread-ləri dəstəkləmək üçün qurulduğunu yoxlayın. Konkret metod — Executors.newVirtualThreadPerTaskExecutor().

Xəta №5: kontekst ötürmək üçün ThreadLocal-a arxalanmaq. Virtual thread-lər tez-tez yaradılıb məhv olunur; ThreadLocal gözlənildiyi kimi davranmaya bilər. Kontekst ötürmək üçün ScopedValue-dan istifadə edin (Scoped Values; ətraflı — növbəti mühazirədə).

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Planetlərarası zonddan məlumatların ötürülməsi 🛰️
Planetlərarası zonddan məlumatların ötürülməsi 🛰️
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Robot-manipulyatorların yığma xəttinin monitorinqi 🦾
Robot-manipulyatorların yığma xəttinin monitorinqi 🦾
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION