1. Asinxron tapşırığın işə salınması: supplyAsync və runAsync
Asinxron tapşırığı işə salmağın ən yayğın yolu — CompletableFuture.supplyAsync-dan istifadə etməkdir. Bu metod nəticə qaytaran lambda və ya metodu qəbul edir. Məsələn, serverdən məlumat yüklənməsini imitasiya etmək istəyirik:
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Uzunmüddətli əməliyyatın imitasiyası (məsələn, fayl yüklənməsi)
sleep(1000);
return "Serverdən məlumatlar";
});
System.out.println("Tapşırıq işə salındı!");
// ... tapşırıq icra olunarkən başqa işlər görə bilərsiniz
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
}
}
runAsync: nəticə lazım olmadıqda
Əgər tapşırığınız heç nə qaytarmırsa (məsələn, sadəcə loqa yazır, bildiriş göndərir), runAsync-dan istifadə edin:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
sleep(500);
System.out.println("Əməliyyat başa çatdı!");
});
runAsync həmişə CompletableFuture<Void> qaytarır, çünki nəticə nəzərdə tutulmur.
2. thenApply, thenAccept, thenRun: fərq nədir?
Asinxron tapşırıq başa çatdıqda, adətən nəticə ilə nəsə etmək istəyirsiniz. Bunun üçün «emalçılar» metodları nəzərdə tutulub:
- thenApply — nəticəni transformasiya edir və yeni nəticə qaytarır.
- thenAccept — nəticəni qəbul edir, heç nə qaytarmır (yan təsirlər üçün istifadə olunur).
- thenRun — nəticəni qəbul etmir, heç nə qaytarmır (sadəcə tapşırıq bitdikdən sonra əməliyyat yerinə yetirir).
thenApply: nəticənin emalı və çevrilməsi
Əgər əvvəlki tapşırığın nəticəsini çevirmək lazımdırsa, thenApply istifadə edin. Məsələn, sətir yükləmisiniz və indi onun uzunluğunu bilmək istəyirsiniz:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Java");
CompletableFuture<Integer> lengthFuture = future.thenApply(s -> {
System.out.println("Sətirin uzunluğu hesablanır...");
return s.length();
});
// lengthFuture indi Integer saxlayır — "Java" sətrinin uzunluğu
lengthFuture.thenAccept(len -> System.out.println("Uzunluq: " + len));
Nələr baş verir:
- future "Java" sətrini saxlayır.
- thenApply sətri onun uzunluğuna (int) çevirir.
- thenAccept nəticəni çıxarır.
thenAccept: nəticə ilə əməliyyat (heç nə qaytarmır)
Nəticə ilə sadəcə nəsə etmək (məsələn, onu ekrana çıxarmaq) lazımdırsa və heç nə qaytarmırsınızsa — thenAccept istifadə edin:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Salam, dünya!");
future.thenAccept(result -> {
System.out.println("Nəticə: " + result);
});
thenAccept “istehlakçı” kimidir: o, nəticəni “yeyir” və onunla faydalı bir şey edir.
thenRun: nəticəsiz əməliyyat
Əgər tapşırıq bitəndən sonra sadəcə nəsə icra etmək istəyirsinizsə, lakin nəticəyə ehtiyac yoxdur, thenRun istifadə edin:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hazırdır!");
future.thenRun(() -> {
System.out.println("Yükləmə başa çatdı!");
});
Qeyd: thenRun daxilində əvvəlki tapşırığın nəticəsini əldə edə bilməzsiniz — o, sadəcə nəzərə alınmır.
3. Çağırış zəncirləri: tapşırıqlardan pipeline qururuq
CompletableFuture-un ən böyük gücü — hesablamalar zəncirini qura bilməsidir. Hər bir metod (thenApply, thenAccept, thenRun) yeni bir CompletableFuture qaytarır və ona yenidən emalçı əlavə etmək olar.
Nümunə: çoxmərhələli emal
Tətbiqimizi təkmilləşdirək: məlumatları yükləyək, onları çevirək, nəticəni çıxaraq və hər şeyin bitdiyini loqa yazaq.
CompletableFuture.supplyAsync(() -> {
System.out.println("Addım 1: Məlumatları yükləyirik...");
sleep(500);
return "Java";
})
.thenApply(data -> {
System.out.println("Addım 2: Məlumatları çeviririk...");
return data.toUpperCase();
})
.thenAccept(result -> {
System.out.println("Addım 3: Nəticəni çıxarırıq: " + result);
})
.thenRun(() -> {
System.out.println("Addım 4: Hər şey başa çatdı!");
});
Konsol çıxışı:
Addım 1: Məlumatları yükləyirik...
Addım 2: Məlumatları çeviririk...
Addım 3: Nəticəni çıxarırıq: JAVA
Addım 4: Hər şey başa çatdı!
Qeyd:
Hər növbəti addım yalnız əvvəlki addım başa çatdıqdan sonra başlayır. Bu, məlumat emalı üçün həqiqi “pipeline-lər” qurmağa imkan verir.
4. Asinxron variantlar: thenApplyAsync, thenAcceptAsync, thenRunAsync
Susmaya görə emalçılar (thenApply, thenAccept, thenRun) əvvəlki tapşırığın bitdiyi eyni Thread-də icra olunur. Bəzən bu əlverişli deyil — emal ağırdırsa, onu ayrı Thread-ə çıxarmaq daha yaxşıdır.
Bunun üçün asinxron versiyalar mövcuddur:
- thenApplyAsync
- thenAcceptAsync
- thenRunAsync
Fərq nədədir?
- Async olmadan: emalçı əvvəlki tapşırığın bitdiyi eyni Thread-də icra oluna bilər (məsələn, tapşırıq ForkJoinPool-da başa çatıbsa, emalçı da orada).
- Async ilə: emalçı mütləq başqa bir Thread-də — ForkJoinPool-dan (və ya sizin Executor-unuzdan) — icra olunacaq.
Nümunə: adi və asinxron emalçını müqayisə edək
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("Yüklənir... [" + Thread.currentThread().getName() + "]");
return "Hello";
});
future.thenApply(result -> {
System.out.println("thenApply: [" + Thread.currentThread().getName() + "]");
return result + " World";
});
future.thenApplyAsync(result -> {
System.out.println("thenApplyAsync: [" + Thread.currentThread().getName() + "]");
return result + " Async World";
});
Tipik çıxış:
Yüklənir... [ForkJoinPool.commonPool-worker-1]
thenApply: [ForkJoinPool.commonPool-worker-1]
thenApplyAsync: [ForkJoinPool.commonPool-worker-2]
Nəticə:
Asinxron emalçı başqa Thread-də icra olunur.
Async metodlardan nə zaman istifadə etməli?
- Emal resurs tutumludursa (məsələn, mürəkkəb hesablamalar, şəbəkə işi).
- Əvvəlki tapşırığın başa çatdığı Thread-i bloklamaq istəmirsinizsə.
- Thread-lərə açıq şəkildə nəzarət etmək istəyirsinizsə (məsələn, ikinci arqument kimi öz Executor-unuzu vermək).
5. Faydalı nüanslar
Cədvəl: thenApply, thenAccept, thenRun metodlarının müqayisəsi
| Metod | Nəticədən istifadə edir? | Dəyər qaytarır? | Nə üçün istifadə olunur |
|---|---|---|---|
|
Bəli | Bəli | Nəticənin çevrilməsi |
|
Bəli | Xeyr | Yan effektlər (çıxış, jurnallaşdırma) |
|
Xeyr | Xeyr | Tapşırıq başa çatdıqdan sonra sadə əməliyyat |
|
Bəli | Bəli | Eyni şey, amma başqa bir Thread-də |
|
Bəli | Xeyr | Eyni şey, amma başqa bir Thread-də |
|
Xeyr | Xeyr | Eyni şey, amma başqa bir Thread-də |
Sual: uzun zəncirlər necə qurulur?
Metodları bir-birinin ardınca, sanki LEGO konstruktoru kimi çağırmaq olar:
CompletableFuture.supplyAsync(() -> "42")
.thenApply(Integer::parseInt)
.thenApply(x -> x * 2)
.thenAccept(x -> System.out.println("Nəticə: " + x));
Çıxış:
Nəticə: 84
Hər növbəti addım əvvəlkinin nəticəsini alır, onu dəyişə və ya sadəcə istifadə edə bilər.
6. thenApply, thenAccept, thenRun ilə işləyərkən tipik səhvlər
Səhv №1: Qaytarılan dəyərlərin tiplərində çaşqınlıq.
thenApply zəncir boyunca ötürüləcək dəyər qaytarmalıdır. Əgər təsadüfən thenApply istifadə edib nəticə qaytarmasanız, növbəti əməliyyat null alacaq (və ya ümumiyyətlə kompilyasiya olunmayacaq). Yan effektlər üçün thenAccept və ya thenRun istifadə edin.
Səhv №2: Nəticədən thenRun daxilində istifadə etməyə cəhd.
thenRun daxilində əvvəlki tapşırığın nəticəsinə çıxış yoxdur. Nəticədən istifadə etmək istəyirsinizsə, thenApply və ya thenAccept seçin.
Səhv №3: Əsas Thread-in bloklanması.
Əgər əsas Thread-də get() və ya join() çağırırsınızsa, asinxronluğun bütün üstünlüklərini itirirsiniz: Thread tapşırığın bitməsini gözləyəcək, sanki köhnə yaxşı sinxron kodda olduğu kimi. Yaxşısı bloklamayan zəncirlər və callback-lərdən istifadə etməkdir.
Səhv №4: Səhvlərin emal olunmaması.
Əgər zəncirdə istisna baş verərsə və siz emalçı əlavə etməmisinizsə (exceptionally, handle, whenComplete), o “itəcək” və tapşırıq görünməyən səhvlə nəticələnə bilər. Həmişə zəncirlərdə səhvləri emal edin.
Səhv №5: Gözlənilmədən başqa Thread-də icra.
Asinxron metodlar (thenApplyAsync və s.) başqa Thread-də icra oluna bilər. Əgər çoxsahəli girişdən qorunmayan dəyişənlərə müraciət edirsinizsə, məlumat yarışları yarana bilər.
GO TO FULL VERSION