CodeGym /Kurslar /JAVA 25 SELF /thenCompose + xüsusi Executor + taym-outlar

thenCompose + xüsusi Executor + taym-outlar

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

1. thenCompose vs. thenApply: fərq nədir və nə vaxt hansını istifadə etməli

Java-da asinxron proqramlaşdırmada (CompletableFuture vasitəsilə) tez-tez əməliyyat zəncirləri icra olunur. Bunun üçün iki oxşar metod var: thenApplythenCompose. Amma onlar fərqli işləyir!

thenApply

thenApply metodundan növbəti addım sadə dəyər çevrilməsi olanda, yəni yeni asinxron əməliyyat başlamayanda istifadə olunur. O, əvvəlki addımın nəticəsini qəbul edir, emal edir və yeni dəyər qaytarır (CompletableFuture deyil).

Stream API ilə tanışsınızsa, thenApply təxminən map kimidir: nəticəni götürür, funksiyanı tətbiq edir və çevrilmiş variantı qaytarır.

Nümunə:

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "42");
CompletableFuture<Integer> lengthFuture = cf.thenApply(s -> s.length());
// lengthFuture 2 saxlayır ("42" sətirinin uzunluğu)

Sadə dildə desək, thenApply — bu, “Nəticə hazır olanda, bununla bunu et” deməyin bir yoludur.

thenCompose

  • Növbəti addım daha bir asinxron əməliyyat olanda istifadə olunur ( CompletableFuture qaytarır).
  • İç-içə CompletableFuture-ları “açmağa” imkan verir (flatMap analoqu).
  • Asinxron funksiyanı thenApply ilə istifadə etsəniz, CompletableFuture<CompletableFuture<T>> əldə edəcəksiniz — əlverişsizdir!

Nümunə:

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "user42");

// Tutaq ki, istifadəçi adına görə onun sifarişlərini almaq lazımdır (asinxron)
CompletableFuture<List<Order>> ordersFuture = cf.thenCompose(username -> fetchOrdersAsync(username));
// fetchOrdersAsync CompletableFuture<List<Order>> qaytarır

Vizual olaraq:

  • thenApply: CF<String>thenApply(s -> s.length())CF<Integer>
  • thenCompose: CF<User>thenCompose(u -> fetchOrdersAsync(u.id))CF<List<Order>>

Nə vaxt hansını istifadə etməli?

  • Funksiya adi dəyər qaytarırsa — thenApply istifadə edin.
  • Funksiya CompletableFuture qaytarırsa — thenCompose istifadə edin.

Səhv nümunəsi:

cf.thenApply(username -> fetchOrdersAsync(username)); // CF<CF<List<Order>>> alacaqsınız
cf.thenCompose(username -> fetchOrdersAsync(username)); // CF<List<Order>> alacaqsınız

2. Thread hovuzuna nəzarət (Executor): niyə və necə öz Executor-unuzu istifadə etməli

Defolt olaraq: ForkJoinPool.commonPool()

CompletableFuture.supplyAsync(...) və ya thenApplyAsync(...) yazanda Executor göstərməsəniz, Java ümumi thread hovuzunu istifadə edir — ForkJoinPool.commonPool(). Bu rahatdır, amma həmişə uyğun deyil:

  • Çox uzun və ya bloklayan əməliyyatlarınız varsa (şəbəkə sorğuları, fayllarla iş), ümumi hovuz “tutilə” bilər və bütün vəzifələr gözləyəcək.
  • Bəzən fərqli prioritetli vəzifələri izolyasiya etmək və ya eyni anda işləyən thread sayını məhdudlaşdırmaq lazımdır.

Öz Executor nə vaxt lazımdır?

  • Uzun, bloklayan əməliyyatlar (məsələn, DB sorğuları, HTTP sorğuları, fayl oxuma).
  • Tapşırıqların izolyasiyası: istifadəçi tapşırıqları sistem tapşırıqlarına mane olmasın.
  • Resursların məhdudlaşdırılması: məsələn, eyni vaxtda 10-dan çox yükləmə başlamamaq.

Öz Executor-unuzu necə yaratmaq olar

Adətən ThreadPoolExecutor və ya Executors-dan fabriklər istifadə olunur:

ExecutorService myExecutor = Executors.newFixedThreadPool(10);

CompletableFuture ilə öz Executor-unuzu necə istifadə etməli

  • supplyAsync, runAsync, thenApplyAsync, thenComposeAsync və digər metodlarda ikinci arqument kimi öz Executor-unuzu verə bilərsiniz.

Nümunələr:

CompletableFuture<String> cf = CompletableFuture.supplyAsync(
    () -> loadDataFromNetwork(), myExecutor
);

cf.thenApplyAsync(data -> processData(data), myExecutor)
  .thenAcceptAsync(result -> System.out.println(result), myExecutor);

Vacibdir: əgər Executor göstərməsəniz, ForkJoinPool.commonPool() istifadə olunacaq.

Defolt Executor nə vaxt kifayətdir?

  • Qısa, CPU-bound vəzifələr üçün (sadə hesablamalar).
  • Vəzifənin hansı threaddə icra olunmasının önəmli olmadığı hallarda.

3. Taym-outların işlənməsi: orTimeout və completeOnTimeout

Asinxron əməliyyatlar ilişib qala və ya çox uzun çəkə bilər (məsələn, server cavab vermirsə). Sonsuza qədər gözləməmək üçün CompletableFuture-də taym-outlarla iş üçün metodlar var.

orTimeout

  • Əməliyyat göstərilən vaxtda tamamlanmasa, CompletableFuture-i TimeoutException istisnası ilə tamamlayır.
  • Real icra olunan vəzifəni dayandırmır, amma downstream-zəncir xətanı alacaq.

Sintaksis:

cf.orTimeout(3, TimeUnit.SECONDS)
  .exceptionally(ex -> {
      System.out.println("Taym-out: " + ex);
      return null;
  });

Nümunə:

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(5000); // uzun əməliyyatı imitasiya edirik
    return "OK";
});

cf.orTimeout(2, TimeUnit.SECONDS)
  .exceptionally(ex -> {
      System.out.println("Xəta: " + ex);
      return "TIMEOUT";
  });

Nəticə:

2 saniyədən sonra TimeoutException atılacaq və exceptionally xətanı emal edəcək.

completeOnTimeout

  • Əməliyyat taym-out müddətində bitməsə, CompletableFuture-i göstərilən dəyər ilə tamamlayır.
  • İstisna atmır, “ehtiyat” dəyər qaytarır.

Sintaksis:

cf.completeOnTimeout("DEFAULT", 2, TimeUnit.SECONDS);

Nümunə:

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(5000);
    return "OK";
});

cf.completeOnTimeout("TIMEOUT", 2, TimeUnit.SECONDS)
  .thenAccept(System.out::println); // 2 saniyədən sonra "TIMEOUT" çap olunacaq

orTimeout və completeOnTimeout müqayisəsi

Metod Taym-out zamanı nə edir? Sonra necə işlənir?
orTimeout
TimeoutException ilə tamamlayır exceptionally/handle vasitəsilə emal etmək olar
completeOnTimeout
Göstərilən dəyər ilə tamamlayır thenAccept/thenApply bu dəyəri alacaq

4. Praktika: thenCompose, xüsusi Executor və taym-out ilə nümunə

Tapşırıq:

  • İstifadəçini id ilə əldə etmək (asinxron, gecikmə ilə).
  • Sonra istifadəçinin sifarişlərini asinxron əldə etmək (yenə gecikmə ilə).
  • Xüsusi Executor istifadə etmək.
  • Sifarişlərin alınmasına taym-out əlavə etmək.
import java.util.concurrent.*;
import java.util.*;

public class AsyncDemo {
    static ExecutorService ioExecutor = Executors.newFixedThreadPool(4);

    // İstifadəçini asinxron almağın imitasiya olunması
    static CompletableFuture<String> fetchUserAsync(int userId) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "user" + userId;
        }, ioExecutor);
    }

    // İstifadəçinin sifarişlərini asinxron almağın imitasiya olunması
    static CompletableFuture<List<String>> fetchOrdersAsync(String username) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(3000); // Uzun əməliyyat!
            return List.of("order1", "order2");
        }, ioExecutor);
    }

    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }

    public static void main(String[] args) {
        fetchUserAsync(42)
            .thenCompose(username ->
                fetchOrdersAsync(username)
                    .orTimeout(2, TimeUnit.SECONDS) // Sifarişləri almağa taym-out
                    .exceptionally(ex -> {
                        System.out.println("Sifarişləri almaq mümkün olmadı: " + ex);
                        return List.of();
                    })
            )
            .thenAccept(orders -> System.out.println("Sifarişlər: " + orders))
            .join(); // Bütün zəncirin tamamlanmasını gözləyirik

        ioExecutor.shutdown();
    }
}

Nə baş verir:

  • İstifadəçini əldə edirik (1 saniyə).
  • Sifarişləri əldə edirik (3 saniyə, amma taym-out 2 saniyədir).
  • Vaxta sığmadıqda — TimeoutException tutulur, boş siyahı qaytarılır.
  • Hər şey xüsusi Executor-da işləyir.

Nəticə:

Sifarişləri almaq mümkün olmadı: java.util.concurrent.TimeoutException
Sifarişlər: []

Əgər gecikməni fetchOrdersAsync-də 1_000 ms-ə azaltsanız — real sifarişləri görəcəksiniz.

5. Tipik səhvlər və incəliklər

Səhv №1: thenApply əvəzinə thenCompose asinxron əməliyyatlar üçün istifadə olunur.
Əgər funksiya CompletableFuture qaytarır, siz isə thenApply tətbiq etmisinizsə, iç-içə tip CompletableFuture<CompletableFuture<T>> alacaqsınız. Bu, zənciri çətinləşdirir və artıq “qablaşdırmalara” gətirib çıxarır. Həll: nəticəni CompletableFuture<T>-ə “yastılaşdırmaq” üçün thenCompose istifadə edin.

Səhv №2: Uzun və ya IO vəzifələrinin Executor olmadan işə salınması.
Defolt olaraq vəzifələr ForkJoinPool.commonPool()-da icra olunur. Onu həddən artıq yükləsəniz, gecikmələr artacaq və tətbiqdəki digər vəzifələr yavaşıya bilər. Həll: öz ExecutorService-inizi yaradın və onu supplyAsync/thenApplyAsync-ə verin.

Səhv №3: orTimeout vəzifənin icrasını ləğv edir deyə düşünmək.
orTimeout yalnız taym-outla CompletableFuture-i istisna ilə tamamlayır, amma vəzifənin özü fon rejimində işləməyə davam edir. Həll: icranı saxlamaq lazımdırsa, cancel(true) və ya öz dayandırma mexanizmlərinizdən istifadə edin.

Səhv №4: Taym-outun tətbiq sahəsini düzgün başa düşməmək.
orTimeoutcompleteOnTimeout yalnız zəncirin konkret bir addımı üçün işləyir, bütün zəncir üçün deyil. Həll: bütün zəncir üçün ümumi taym-out lazımdırsa, onu ayrıca bir CompletableFuture ilə əhatə edin və taym-outu ona tətbiq edin.

Səhv №5: ExecutorService bağlanmır.
Vəzifələrdən sonra ExecutorService üçün shutdown()/shutdownNow() çağırmasanız, threadlər işləməyə davam edəcək və proqram “asılı qala” bilər. Həll: həmişə ExecutorService-i finally-də bağlayın və ya Java 21+ versiyasında try-with-resources istifadə edin.

1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Asinxron proqramlaşdırma
Asinxron proqramlaşdırma
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION