1. Sinxron kodun problemi
Təsəvvür edin: sizdə internetdən məlumat yükləməli və ya böyük faylı oxumalı bir proqram var. Siz buna bənzər bir şey yazırsınız:
String data = readFromFile("bigfile.txt");
System.out.println("Məlumat: " + data);
Hər şey yaxşı olardı, amma fayl böyükdürsə və ya şəbəkə yavaşdırsa, proqram oxuma sətrində sadəcə donur. İstifadəçi “donmuş” interfeysə baxır, server digər sorğuları emal edə bilmir, proqramçı isə... kədərlənir.
Bu vəziyyətə bloklama deyilir: axın (məsələn, tətbiqinizin əsas axını) əməliyyat bitənədək gözləməyə məcbur olur. Belə əməliyyatlar çoxdursa — oldu, salam, gecikmələr və aşağı məhsuldarlıq.
Bu, sanki kafeyə gəlib sifariş verirsiniz və... barista qəhvəni hazırlayana qədər stendin yanında dayanmalı olursunuz. Digər müştərilər də arxanızda dayanıb barista sizlə işini bitirənədək gözləyirlər. Qeyri-səmərəli, elə deyilmi?
Asinxronluq: bizi necə xilas edir
Asinxron proqramlaşdırma — uzunmüddətli əməliyyatlar (məsələn, fayl oxuma, serverə sorğu, verilənlər bazasına müraciət) fon thread-ində icra olunur, əsas axın isə işləməyə davam edir: istifadəçilərə xidmət göstərir, yeni sorğuları qəbul edir, hadisələrə reaksiya verir.
Yəni siz sifarişi edirsiniz (tapşırığı işə salırsınız), öz işlərinizlə məşğul olursunuz, qəhvə hazır olanda (tapşırıq başa çatanda) sizə sadəcə deyirlər: “Hazırdır!”
Java-da CompletableFuture yaranana qədər bununla işləmək o qədər də rahat deyildi. Gəlin necə inkişaf etdiyini görək.
2. Tarixi yanaşmalar: Future və onun məhdudiyyətləri
Java 5-də Future interfeysi yarandı — asinxron tapşırıqlarla işi bir az da olsa rahatlaşdırmağa ilk cəhd. O, tapşırığı thread hovuzuna həvalə etməyə və nə vaxtsa nəticəni almağa imkan verirdi.
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> 2 + 2);
int result = future.get(); // Diqqət: tapşırıq tamamlanana qədər axın bloklanacaq!
Fikir yaxşı görünürdü, amma təcrübədə məlum oldu ki, Future köhnə poçt qutusuna bənzəyir: məktubu göndərirsiniz, lakin cavab gəlib-gəlmədiyini bilmək üçün daim içinə baxmalısınız.
O, nəticə hazır olanda xəbər verməyi bacarmır, zəncirvari “bunu et, sonra onu” hərəkətlərini dəstəkləmir, xətaları səliqəli şəkildə emal etməyə şərait yaratmır. Hər şey həmin bloklayan get() çağırışına dirənir, nəticədə asinxronluq yenidən gözləməyə çevrilir.
3. CompletableFuture-un yaranması: asinxronluğun yeni üslubu
Java 8-də köhnəlmiş Future-un yerinə həqiqi asinxronluq qəhrəmanı — CompletableFuture gəldi. Bu sinif java.util.concurrent paketindən olub, nəticələri “əl ilə” gözləməkdən yorulan və asinxron kodu daha səliqəli, yığcam və anlaşılan şəkildə yazmaq istəyən hər kəs üçün universal alət oldu.
CompletableFuture demək olar ki, hər şeyi bacarır. O, tapşırıqları digər axınlarda işə sala, onlardan zəncirlər qura bilər — məsələn, əvvəlcə nəticəni hesablamaq, sonra onu emal etmək, ardınca daha bir iş görmək. O, bir neçə tapşırığı asanlıqla birləşdirir: hamısının birgə başa çatmasını və ya birincinin tamamlanmasını gözləmək olar. Xətalar da zərif şəkildə emal olunur — artıq əlavə try-catch yazmağa ehtiyac yoxdur. Üslub isə daha funksional olur: darıxdırıcı çağırışlar və gözləmələr əvəzinə thenApply, thenAccept kimi ifadəli metodlar meydana çıxır.
flowchart LR
A[Tapşırığın asinxron işə salınması] --> B[Nəticənin emalı]
B --> C[Növbəti əməliyyat]
C --> D[Xətaların emalı]
Beləcə, CompletableFuture asinxronluğu ağır sənətdən rahat və çevik bir alətə çevirdi, onunla kod nəhayət sərbəst nəfəs alır.
4. Ən sadə nümunə: CompletableFuture dünyasına ilk addım
Gəlin asinxron tapşırığın minimal nümunəsinə baxaq:
import java.util.concurrent.CompletableFuture;
public class AsyncDemo {
public static void main(String[] args) {
// Tapşırığı asinxron işə salırıq
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2 + 2);
// Nəticəni alırıq (axını bloklayır!)
try {
int result = future.get();
System.out.println("Nəticə: " + result); // 4
} catch (Exception e) {
e.printStackTrace();
}
}
}
Bu kod artıq hesablamağı ayrı bir axında icra edir — tapşırıq işə salınarkən əsas axın bloklanmır. Amma çağırış get() yenə də nəticə hazır olana qədər axını bloklayır.
Bəs axını bloklamamaq üçün nə etməli?
Çox sadədir: tapşırıq bitəndə çağırılan callback metodlarından istifadə edin:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2 + 2);
future.thenAccept(result -> System.out.println("Nəticə: " + result));
System.out.println("Mən bloklanmıram və başqa işlər görə bilirəm!");
Nəticə:
- thenAccept — “nəticəyə abunə ol”: tapşırıq tamamlananda bu kodu çağır.
- Əsas axın tapşırığın tamamlanmasını gözləmir, işləməyə davam edir.
Vizualizasiya (hadisələrin psevdokodu)
[Əsas axın] --> [Tapşırığın işə salınması]
| |
v v
[Nəsə edir] [Fon axını 2+2-ni hesablayır]
| |
v v
[Çap edir "Mən bloklanmıram..."]
| |
v v
[Hesablandıqda — thenAccept çağırılır]
5. Bu tətbiqdə necə görünür?
Təsəvvür edin ki, siz konsol tətbiqi hazırlayırsınız: istifadəçi məlumatların yüklənməsini sifariş edə bilər (məsələn, bazadan və ya serverdən), məlumatlar yüklənərkən isə proqram “donmur”, əmrləri qəbul etməyə davam edir.
Nümunə: uzun əməliyyatın imitasiya edilməsi
import java.util.concurrent.CompletableFuture;
public class AsyncApp {
public static void main(String[] args) {
System.out.println("Məlumatların yüklənməsinə başlayırıq...");
CompletableFuture<String> dataFuture = CompletableFuture.supplyAsync(() -> {
// Uzun yükləmənin imitasiyası
try {
Thread.sleep(2000); // 2 saniyə
} catch (InterruptedException e) {
return "Yükləmə xətası";
}
return "Məlumatlar uğurla yükləndi!";
});
// Nəticəyə abunə oluruq
dataFuture.thenAccept(result -> System.out.println("Nəticə: " + result));
// Proqram işləməyə davam edir
System.out.println("Məlumatlar yüklənərkən mən başqa işlər görə bilərəm!");
// Proqramın vaxtından əvvəl bitməməsi üçün (yalnız demo üçün!)
try {
Thread.sleep(2500);
} catch (InterruptedException ignored) {}
}
}
Konsolda nə görəcəksiniz:
Məlumatların yüklənməsinə başlayırıq...
Məlumatlar yüklənərkən mən başqa işlər görə bilərəm!
[2 saniyədən sonra]
Nəticə: Məlumatlar uğurla yükləndi!
6. Faydalı incəliklər
Qapağın altında axınlar barədə bir az
CompletableFuture.supplyAsync(...) yazdığınızda, tapşırıq susmaya görə ForkJoinPool adlanan xüsusi thread hovuzunda icra olunur — Java bunu paralel tapşırıqlar üçün istifadə edir. Daha çox nəzarət lazımdırsa (məsələn, öz ExecutorService-iniz), onu ikinci parametr kimi verə bilərsiniz:
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2 + 2, executor);
Amma sadə tapşırıqlar üçün standart hovuz kifayətdir.
Nəticənin alınması: get(), join(), thenAccept
- get() — nəticə hazır olana qədər axını bloklayır (checked istisnalar atır).
- join() — o da bloklayır, amma unchecked istisnalar atır (RuntimeException).
- thenAccept(), thenApply() və digərləri — bloklamır, nəticə hazır olduqda ötürülən funksiyanı çağırır.
Real asinxron tətbiqlərdə əsas thread-də get()/join() istifadəsindən çəkinin!
7. CompletableFuture ilə ilk addımlarda tipik səhvlər
Səhv № 1: Əsas thread-də get() və ya join() istifadəsi.
Beləcə, proqramı yenidən bloklayırsınız və asinxronluğun bütün üstünlüklərini itirirsiniz. Bunun əvəzinə nəticəni emal etmək üçün thenAccept, thenApply və digər metodlardan istifadə edin.
Səhv № 2: Xətanı emal etməyi unutmaq.
Əgər asinxron tapşırıqda istisna baş verərsə, o, əsas axına “çıxmayacaq”. exceptionally və ya handle vasitəsilə emal etməsəniz, nələrin səhv getdiyini ümumiyyətlə bilməyəcəksiniz.
Səhv № 3: Proqramın bitməsini gözləməmək.
Demo nümunələrdə tez-tez main-axını Thread.sleep ilə “yavaşıtmaq” lazım olur — yoxsa proqram tapşırıqdan əvvəl tamamlanar. Real tətbiqlərdə (məsələn, veb serverlərdə) bu problem deyil, lakin konsol demolarında bunu nəzərə alın.
Səhv № 4: thenAccept və thenApply qarışdırılır.
thenAccept — “yan təsirlər” üçündür (heç nə qaytarmır), thenApply — nəticənin çevrilməsi üçündür (yeni nəticə qaytarır).
Səhv № 5: Asinxron və sinxron kodu səbəbsiz qarışdırmaq.
Asinxron yazmağa başlamısınızsa — yalnız ən son çarə kimi (məsələn, testlərdə) istisna olmaqla, get()/join() ilə yenidən sinxrona çəkməyin.
GO TO FULL VERSION