CodeGym /Kurslar /JAVA 25 SELF /CompletableFuture-lərin kombinasiyası: thenCombine, allOf...

CompletableFuture-lərin kombinasiyası: thenCombine, allOf, anyOf

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

1. CompletableFuture-ləri kombinə edirik

Real həyatda hər şey nadir hallarda tək mənbədən gəlir: istifadəçi profili və onun sifarişlərini paralel yükləmək, iki mikroxidmətin məlumatlarını — birləşdirmək olar, bəzən isə sadəcə ilk gələn cavabı emal etmək istəyirik. Sinxron yanaşma burada növbə ilə gözləməyə məcbur edir; CompletableFuture sinfi hər şeyi eyni anda işə salmağa və nəticələri zərif şəkildə kombinə etməyə imkan verir. Bunun üçün xüsusi metodlar təqdim edir: thenCombine, allOf, anyOf. Gəlin hər birini ardıcıllıqla nəzərdən keçirək.

Paralel tapşırıqlar: iki asinxron sorğu

Bu, sinxron şəkildə necə görünərdi:

String name = loadUserName();    // uzun çəkir
int balance = loadUserBalance(); // uzun çəkir
System.out.println("Ad: " + name + ", Balans: " + balance);

Problem: ikinci çağırış yalnız birincisi bitəndən sonra başlayacaq.

Asinxron yanaşma

CompletableFuture vasitəsilə hər iki tapşırığı eyni vaxtda başlatmaq olar:

CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> loadUserName());
CompletableFuture<Integer> balanceFuture = CompletableFuture.supplyAsync(() -> loadUserBalance());

Bəs indi hər iki nəticəni necə alıb birlikdə emal edək? Bunun üçün thenCombine istifadə olunur.

2. thenCombine: iki tapşırığın nəticələrini birləşdiririk

thenCombine metodu iki CompletableFuture-u birləşdirməyə və hər iki tapşırıq tamamlandıqda əməliyyat icra etməyə imkan verir. O, birləşdirmənin nəticəsi ilə yeni CompletableFuture qaytarır.

Siqnatura:

<A, B, C> CompletableFuture<C> thenCombine(
    CompletionStage<? extends B> other,
    BiFunction<? super A, ? super B, ? extends C> fn
)
  • A — birinci future-un nəticəsinin tipi,
  • B — ikincinin tipi,
  • C — birləşdirilmiş nəticənin tipi.

Nümunə

CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> loadUserName());
CompletableFuture<Integer> balanceFuture = CompletableFuture.supplyAsync(() -> loadUserBalance());

CompletableFuture<String> resultFuture = nameFuture.thenCombine(
    balanceFuture,
    (name, balance) -> "Ad: " + name + ", Balans: " + balance
);

resultFuture.thenAccept(System.out::println);

Bu necə işləyir:

  • Hər iki future paralel başlayır.
  • Hər ikisi bitən kimi (name, balance) -> ... funksiyası çağırılır.
  • Nəticə future-u nəticə sətirini saxlayır.

Rəqəmlərlə mini-nümunə

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 3);
CompletableFuture<Integer> sum = f1.thenCombine(f2, Integer::sum);

sum.thenAccept(result -> System.out.println("Cəm: " + result));

Çıxış:

Cəm: 5

Asinxron variant

Əgər birləşdirmə ağır əməliyyatdırsa, thenCombineAsync istifadə edin:

f1.thenCombineAsync(f2, (a, b) -> a * b);

3. allOf: tapşırıq çox olduqda

Bəs əgər iki yox, düz on tapşırığımız varsa? Məsələn, eyni anda on istifadəçi haqqında məlumatı paralel yükləmək istəyirik. Bunun üçün CompletableFuture.allOf metodu var.

Təsvir

CompletableFuture.allOf(f1, f2, ..., fn) yeni future qaytarır və o, bütün ötürülən tapşırıqlar tamamlandıqda tamamlanır. Amma bir nüans var: bu future nəticə saxlamır — onun tipi həmişə CompletableFuture<Void>-dur. Nəticələri almaq üçün onları ilkin future-lardan ayrıca «çıxarmaq» lazımdır.

Nümunə

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Birinci");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "İkinci");

CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);

all.thenRun(() -> {
    // Bütün tapşırıqlar tamamlandı!
    String s1 = f1.join(); // join() — get() kimidir, lakin unchecked exception atır
    String s2 = f2.join();
    System.out.println(s1 + " & " + s2);
});

Çıxış:

Birinci & İkinci

Tapşırıqlar massiviylə nümunə

List<CompletableFuture<String>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    int id = i;
    futures.add(CompletableFuture.supplyAsync(() -> "İstifadəçi " + id));
}

CompletableFuture<Void> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

all.thenRun(() -> {
    for (CompletableFuture<String> f : futures) {
        System.out.println(f.join());
    }
});

Nə baş verir:

  • Bütün tapşırıqlar paralel başlayır.
  • allOf bütün tapşırıqlar bitəndə tamamlanacaq.
  • thenRun blokunda nəticələri join() vasitəsilə götürə bilərik.

Vizual sxem

[Future1]   \
[Future2] ----> [allOf] ---> thenRun
[Future3]   /

4. anyOf: ilk tamamlanan tapşırığı gözləyirik

Bəzən hamısını gözləmək lazım deyil, ən sürətli tapşırığın nəticəsini almaq kifayətdir. Məsələn, iki serverdən məlumat istəyirik — kim daha tez cavab verirsə, onun məlumatını istifadə edirik. Bunun üçün CompletableFuture.anyOf var.

Təsvir

CompletableFuture.anyOf(f1, f2, ..., fn) elə bir future qaytarır ki, ötürülən tapşırıqlardan istənilən biri tamamlanan kimi o da tamamlanır. Nəticənin tipi — CompletableFuture<Object>, çünki tapşırıqların tipləri fərqli ola bilər.

Nümunə

CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {
    sleep(500);
    return "Sürətli server";
});
CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
    sleep(2000);
    return "Yavaş server";
});

CompletableFuture<Object> any = CompletableFuture.anyOf(fast, slow);

any.thenAccept(result -> System.out.println("Aldıq: " + result));

Çıxış:

Aldıq: Sürətli server

Müxtəlif tiplərlə nümunə
Fərqli tiplərdə tapşırıqları kombinə etmək olar, lakin bu halda nəticə Object tipində olacaq və açıq çevirmə (cast) tələb olunacaq.

5. Faydalı nüanslar

Tövsiyələr

  • allOf nəticələrin massivini qaytarmır. Dəyərləri join() və ya get() ilə almaq üçün ilkin future-ları saxlamaq lazımdır.
  • anyOf ilk tamamlanan nəticəni qaytarır, lakin tip Object-dir. Əgər bütün tapşırıqlar eyni tipdədirsə, çevirmə edə bilərsiniz.
  • Əgər allOf-dakı tapşırıqlardan biri səhvlə tamamlanarsa — yekun future də səhvlə tamamlanacaq.
  • thenCombine üçün hər iki tapşırıq uğurla tamamlanmalıdır, əks halda — istisna.

Metodların müqayisə cədvəli

Metod Nə zaman istifadə etmək Nəticə tipi
thenCombine
İki tapşırığın nəticələrini birləşdirmək lazım olduqda Birləşdirmənin nəticəsi
allOf
Bütün tapşırıqların tamamlanmasını gözləmək lazım olduqda
Void
anyOf
Hər hansı bir tapşırığın tamamlanmasını gözləmək kifayət olduqda Object (ilk tapşırığın nəticəsi)

6. CompletableFuture-ləri kombinə edərkən tipik səhvlər

Xəta № 1: Nəticəni əsas axında get()/join() ilə gözləmək.
Əgər asinxron kod yazırsınızsa, amma sonda yenə də get() və ya join() çağırırsınızsa, axını bloklayırsınız və asinxronluğun üstünlüklərini itirirsiniz. Bloklamadan nəticəni emal etmək üçün thenAccept/thenRun istifadə etmək daha yaxşıdır.

Xəta № 2: allOf istifadə edərkən ilkin future-lara istinadları saxlamamaq.
Əgər CompletableFuture.allOf(f1, f2, f3) çağırmısınızsa, amma f1, f2, f3-ü saxlamamısınızsa — onların nəticələrini ala bilməyəcəksiniz. allOf yalnız Void qaytarır!

Xəta № 3: Zəncirdə səhvləri emal etməmək.
Əgər tapşırıqlardan biri səhvlə tamamlanarsa, bütün allOf və ya thenCombine də səhvlə tamamlanacaq. Səhvləri qaçırmamaq üçün (exceptionally, handle, whenComplete) kimi metodlardan istifadə edin.

Xəta № 4: anyOf zamanı tiplərin uyğun gəlməməsi.
anyOf Object qaytarır. Əgər future-lar fərqli tiplər qaytarırsa, ilk gələnin nə olduğunu müəyyənləşdirməli olacaqsınız. Mümkünsə, eyni tipli tapşırıqlardan istifadə etmək daha yaxşıdır.

Xəta № 5: Şərhsiz həddən artıq mürəkkəb zəncirlər.
Kod çoxaldıqda, future zəncirləri «spagetti»yə çevrilə bilər. Zənciri ayrı dəyişənlərə bölməkdən və addımları şərh etməkdən çəkinməyin.

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