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 1–2 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 000–2 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 000 – 10 000 | Yüksək | Uzun çəkir |
| Virtual | 100 000 – 1 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, Semaphore və java.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: JVisualVM və JFR.
GO TO FULL VERSION