CodeGym /Kurslar /JAVA 25 SELF /Virtual Threads-in miqyaslana bilməsi və məhsuldarlığı

Virtual Threads-in miqyaslana bilməsi və məhsuldarlığı

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

1. Miqyaslana bilmə

Niyə adi thread-lər zəif miqyaslanır?

Hər bir klassik thread (Thread) — OS səviyyəli obyekt olub, öz stack-i (adətən 12 MB) və vəziyyət strukturu var. Məsələn, 10 000 adi thread yaratmağa cəhd çox vaxt OutOfMemoryError xətasına gətirib çıxarır. Buna görə ənənəvi serverlərdə məhdud sayda thread pool-larından istifadə olunur.

Virtual thread-lər: miqyası mümkün edən “sehr”

Virtual thread-lər (Java 21+) — JVM tərəfindən idarə olunan “yüngül” thread-lərdir. Onların stack-i heap-də saxlanır və dinamik olaraq böyüyüb/kiçilə bilir. Thread I/O-da bloklananda JVM onu “dondurur” və digər tapşırıqları icra etməyə davam edir.

JVM daxilində kiçik bir “daşıyıcı” (carrier threads) pulu — OS-in platforma thread-ləri — işləyir; virtual thread-lər növbə ilə bu daşıyıcılarda icra olunur. Bu, yaddaş dəhşəti olmadan 100 000+ tapşırıq yaratmağa imkan verir. Hansı virtual thread-in nə vaxt icra olunacağını JVM özü planlayır.

Demonstrasiya: 100_000 virtual thread-lər vs 1_000 platforma thread-ləri

Nümunə: 1000 adi thread-in yaradılması

// 1000 adi thread yaratma cəhdi
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    Thread t = new Thread(() -> {
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
    threads.add(t);
    t.start();
}
System.out.println("Yaradılan thread sayı: " + threads.size());

Nəticə: Əksər sistemlərdə 1 0002 000 thread yaratmaq mümkün olacaq; daha böyük dəyərlərdə yaddaş problemləri və ləngimələr başlayacaq.

Nümunə: 100 000 virtual thread-in yaradılması

// 100_000 virtual thread yaradırıq
List<Thread> vThreads = new ArrayList<>();
for (int i = 0; i < 100_000; i++) {
    Thread t = Thread.ofVirtual().start(() -> {
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
    vThreads.add(t);
}
System.out.println("Yaradılan virtual thread sayı: " + vThreads.size());

Nəticə: Proqram asanlıqla 100 000 virtual thread yaradır — nə çökmə var, nə də ciddi ləngimə. Yaddaş sərfi dəfələrlə azdır.

Vizual müqayisə

Thread tipi Maks. thread sayı (təxmini) Yaddaş istifadəsi Başlatma vaxtı
Adi (Thread) 1 00010 000 Yüksək Uzun çəkir
Virtual 100 0001 000 000+ Aşağı Ani

Fakt: Virtual thread-lər mürəkkəb pool-lara və sistemin yüklənmə riskinə ehtiyac olmadan “tapşırıq üçün bir thread” yanaşması ilə kod yazmağa imkan verir.

2. Məhsuldarlıq: virtual thread-lərin parladığı yerlər

I/O-ya dayanan tapşırıqlar (I/O-bound)

Virtual thread-lər şəbəkə sorğuları, fayl I/O-su və DB ilə iş üçün əladır. Əməliyyat bloklananda virtual thread “daşıyıcını” azad edir, JVM isə digər tapşırıqları icra edir. Bu, çoxsaylı eyni vaxtlı gözləmələrdə keçirmə qabiliyyətini artırır.

Nümunə: eyni vaxtda 10 000 HTTP sorğusunun imitasiya edilməsi

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();
List<Thread> threads = new ArrayList<>();

for (int i = 0; i < 10_000; i++) {
    Thread t = Thread.ofVirtual().start(() -> {
        try {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com"))
                .build();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println("Cavab: " + response.statusCode());
        } catch (Exception e) {
            System.out.println("Səhv: " + e.getMessage());
        }
    });
    threads.add(t);
}

// Bütün thread-lərin tamamlanmasını gözləyirik
for (Thread t : threads) {
    t.join();
}

Nəticə: Bütün 10 000 sorğu paralel yerinə yetirilir; proqram çökmür və kod sadə qalır.

CPU-bound: virtual thread-lər hesablamaları sürətləndirmir

Tapşırıq CPU-ya yük verirsə, virtual thread-lər sürət qazandırmır: nüvələrin sayı sabittir. Burada nüvələrin sayına bərabər sabit pool-lar istifadə etmək məqsədəuyğundur ki, artıq rəqabət yaranmasın.

// Hər bir tapşırıq böyük diapazonun cəmini hesablayır
Runnable cpuTask = () -> {
    long sum = 0;
    for (int i = 0; i < 100_000_000; i++) {
        sum += i;
    }
    System.out.println("Cəm: " + sum);
};

// Hesablama ilə 1000 virtual thread işə salırıq
for (int i = 0; i < 1000; i++) {
    Thread.ofVirtual().start(cpuTask);
}

Nəticə: Thread-lər CPU uğrunda rəqabət aparacaq, lakin sürətlənmə olmayacaq — bu, Virtual Threads üçün iş deyil.

3. Məhdudiyyətlər və xüsusiyyətlər

Sinxronizasiya və virtual thread-lərin tələləri

  • Native bloklanmalarla ehtiyatlı olun. synchronized istifadəsi virtual thread-i daşıyıcıya “yapışdıra” bilər və faydanı azaldar. Virtual thread-lər üçün optimallaşdırılmış ReentrantLock, Semaphorejava.util.concurrent-dəki digər primitivlərə üstünlük verin.
  • Köhnə kitabxanalar. Bəzi JDBC drayverləri və native kitabxanalar hələ Virtual Threads üçün optimallaşdırılmayıb. Bloklayan əməliyyatları diqqətlə test edin.

Uzunömürlü tapşırıqlar üçün deyil

Virtual Threads “qısa” iş vahidləri üçün idealdır: sorğunun işlənməsi, tək əməliyyat və yekun. Milyonlarla sonsuz yaşayan tapşırıq (məsələn, bitməyən hesablamalar) fayda verməyəcək — onlar üçün platforma thread-lərindən istifadə edin.

4. Best practices: virtual thread-ləri harada istifadə etməli

  • I/O-bound tapşırıqlar: şəbəkə çağırışları, fayllar, DB — thread-in tez-tez gözlədiyi hər yer.
  • Web serverlər: hər HTTP sorğusunu ayrıca virtual thread-də işlədin.
  • İnteqrasiya testləri: minlərlə müştərini sürətlə imitasiya edin.
  • Asinxron emal: alışdığınız “bloklayan” kodu yazın — JVM ağıllı planlaşdırmanı özü edir.

İstifadə etməyə dəyməz:

  • Daim CPU-ya yük verən tapşırıqlar üçün.
  • Aşağı səviyyəli kitabxanalarla uyğunluq kritikdirsə (hələ hamısı adaptasiya olunmayıb).

Qapax altında istifadə üçün rahat executor: Executors.newVirtualThreadPerTaskExecutor() — “hər tapşırıq üçün bir virtual thread”, sabit pool olmadan.

5. Monitorinq və ölçmə: virtual thread-ləri iş başında necə görmək olar

JVisualVM və Flight Recorder

JVisualVM aktiv thread-ləri, onların vəziyyətlərini və yaddaşı göstərir; Java 21-dən etibarən virtual thread-lər ayrıca görünür. Java Flight Recorder (JFR) icranın ətraflı “qara qutu”sunu yazır, Virtual Threads üzrə statistikaları da daxil olmaqla — dar boğazları tapmaq üçün rahatdır.

Kodda thread sayına necə baxmaq olar

JVM-də thread sayını görməyin sadə yolu:

System.out.println("Cəmi thread: " + Thread.activeCount());

Onlardan neçəsinin virtual olduğunu hesablamaq:

long vCount = Thread.getAllStackTraces().keySet().stream()
    .filter(Thread::isVirtual)
    .count();
System.out.println("Virtual thread-lər: " + vCount);

6. Virtual thread-lərlə işləyərkən tipik səhvlər

Xəta №1: Virtual thread-lərin ağır hesablamalar üçün istifadəsi. CPU-bound tapşırıqlarla milyonlarla virtual thread başlatmaq prosessoru sürətləndirməyəcək. Virtual Threads hesablamalar üçün “turbo” deyil.

Xəta №2: Köhnə pattern-lərin kor-koranə köçürülməsi. Virtual thread-lər üçün sabit pool-lar yaratmayın. İstifadə edin: Executors.newVirtualThreadPerTaskExecutor() və JVM-ə avtomatik miqyaslanmağa imkan verin.

Xəta №3: Dəstəklənməyən kitabxanaların istifadəsi. Loom üçün adaptasiya olunmamış native bloklanmalar və kitabxanalar ilişmələrə və performans düşüşünə səbəb ola bilər. Uyğunluğu öncədən yoxlayın.

Xəta №4: Vaxtından əvvəl optimizasiya. Bir neçə thread və adi çoxaxınlılıqla işləyirsinizsə, hər şeyi tələsik Virtual Threads-ə köçürməyin. Bu alət kütləvi I/O və gözləmə olan yerlərdə yaxşıdır.

Xəta №5: Monitorinqi görməməzlikdən gəlmək. Bir milyon tapşırıq yaratmaq asandır, lakin monitorinq və istisnaların emalı olmadan etibarlı sistem əvəzinə “gözəl benchmark” ala bilərsiniz. İstifadə edin: JVisualVMJFR.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION