Eğitim Portalı/Java/CompletableFuture (Asenkron Programlama)
Java01-java/78-completablefuture

CompletableFuture (Asenkron Programlama)

Modern uygulamalar sürekli bekler: bir API'den yanıt, veritabanından sonuç, dosyadan veri. Bu beklemeleri tek tek, sırayla yapmak (senkron) zaman kaybıdır — özellikle birbirinden bağımsız işleri paralel yürütebilecekk…

CompletableFuture (Asenkron Programlama)

Modern uygulamalar sürekli bekler: bir API'den yanıt, veritabanından sonuç, dosyadan veri. Bu beklemeleri tek tek, sırayla yapmak (senkron) zaman kaybıdır — özellikle birbirinden bağımsız işleri paralel yürütebilecekken. CompletableFuture (Java 8), asenkron görevleri başlatma, sonuçlarını dönüştürme, birleştirme ve hataları yönetme için zengin, zincirlenebilir bir API sunar. Concurrency bölümünde thread'leri gördük; burada onların üzerine kurulu yüksek seviyeli asenkron modeli ele alıyoruz.

Asenkron görev başlatma ve dönüştürme

CompletableFuture.supplyAsync(() -> hesapla())   // arka planda çalış, sonuç döndür
    .thenApply(x -> x * 2)                        // sonucu dönüştür
    .thenAccept(System.out::println);             // sonucu tüket (yan etki)
  • supplyAsync(fn): Bir görevi arka planda (ayrı thread) çalıştırır ve bir sonuç üretir.
  • runAsync(r): Sonuç üretmeyen asenkron görev.
  • thenApply(fn): Sonucu dönüştürür (yeni değer döndürür).
  • thenAccept(c): Sonucu tüketir (yan etki, değer yok).
  • thenCompose(fn): Bir asenkron işin sonucuyla başka bir asenkron iş başlatır (zincir düzleştirme — flatMap gibi).
  • join() / get(): Sonucun gelmesini bekler (get checked exception atar).

Örnek 1 (./Ornek1.java) supplyAsyncthenApplythenComposethenAccept zincirini gösterir.

Paralel birleştirme

CompletableFuture'ın asıl gücü, bağımsız işleri paralel yürütüp sonuçlarını birleştirmektir:

f1.thenCombine(f2, (a, b) -> a + b);   // iki işi PARALEL çalıştır, sonuçları birleştir
CompletableFuture.allOf(f1, f2, f3).join();  // hepsinin bitmesini bekle
CompletableFuture.anyOf(f1, f2).join();      // ilk biteni bekle

thenCombine ile iki 200 ms'lik iş toplam ~200 ms sürer (paralel), sırayla yapılsa 400 ms olurdu. Örnek 2 (./Ornek2.java) thenCombine, allOf ve hata yönetimini gösterir.

Hata yönetimi

Asenkron zincirde hatalar try-catch ile yakalanamaz (farklı thread'de olur). Bunun yerine:

future.exceptionally(ex -> yedekDeger);          // hata olursa yedek değer
future.handle((sonuc, ex) -> ex != null ? ... : sonuc);  // hem başarı hem hata
future.whenComplete((sonuc, ex) -> { ... });     // tamamlanınca (sonucu değiştirmez)

Örnek 2 exceptionally ile bir servis hatasını yakalayıp yedek değer döndürür.

Executor seçimi

Varsayılan olarak görevler ForkJoinPool.commonPool()'da çalışır. Bu, CPU yoğun işler için uygundur ama G/Ç yoğun (bloklayan) işlerde havuzu tıkayabilir. Bu durumda kendi executor'ını ver:

ExecutorService exec = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> apiCagir(), exec);

Java 21 ile sanal thread'ler (Executors.newVirtualThreadPerTaskExecutor()) G/Ç yoğun asenkron işler için ideal bir executor olur — binlerce bloklayan görevi ucuza yönetir.

Nerede kullanılır?

  • Bağımsız API çağrılarını paralel yapıp toplam gecikmeyi düşürmek (mikroservisler).
  • Bir sonucu birden çok adımda asenkron işlemek (pipeline).
  • Spring'in @Async metotları CompletableFuture döndürebilir; reaktif programlama (WebFlux) bunun daha ileri bir modelidir.

Özet

CompletableFuture ile asenkron görev başlatma ve dönüştürmeyi (supplyAsync/thenApply/ thenCompose; Örnek 1); paralel birleştirme (thenCombine/allOf) ve hata yönetimini (exceptionally; Örnek 2) öğrendik; executor seçimi ve sanal thread bağlantısına değindik. Asenkron programlama, modern yüksek-performanslı uygulamaların temelidir. Sırada, programa dışarıdan veri vermek: komut satırı argümanları.

Kod Örnekleri(2)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: CompletableFuture — asenkron görevler ve sonuçları zincirleme.
2// Çalıştırma: java Ornek1.java
3import java.util.concurrent.CompletableFuture;
4
5public class Ornek1 {
6
7    public static void main(String[] args) throws Exception {
8        long t0 = System.currentTimeMillis();
9
10        // supplyAsync: bir görevi ARKA PLANDA (ayrı thread) çalıştır, bir sonuç döndür.
11        CompletableFuture<Integer> gelecek = CompletableFuture
12                .supplyAsync(() -> {
13                    yavasIs(200);
14                    return 10;                      // ham veri
15                })
16                .thenApply(n -> {                   // sonucu DÖNÜŞTÜR (n -> n*2)
17                    System.out.println("  thenApply: " + n + " -> " + (n * 2));
18                    return n * 2;
19                })
20                .thenApply(n -> n + 5);             // tekrar dönüştür
21
22        // join: sonucun gelmesini bekle (bloklar). get() de benzer (checked exception atar).
23        Integer sonuc = gelecek.join();
24        System.out.println("Sonuç: " + sonuc + "  (~" + (System.currentTimeMillis() - t0) + " ms)");
25
26        // thenCompose: bir asenkron işin sonucuyla BAŞKA bir asenkron iş başlat (zincir).
27        CompletableFuture<String> zincir = CompletableFuture
28                .supplyAsync(() -> "kullanici42")
29                .thenCompose(Ornek1::profilGetir);  // önceki sonuç -> yeni CompletableFuture
30        System.out.println("thenCompose: " + zincir.join());
31
32        // thenAccept: sonucu TÜKET (yan etki, değer döndürmez)
33        CompletableFuture.supplyAsync(() -> "rapor hazır")
34                .thenAccept(r -> System.out.println("thenAccept: " + r))
35                .join();
36
37        System.out.println("""
38
39                --- CompletableFuture ---
40                Asenkron (bloklamayan) görevleri ve sonuçlarının dönüşümünü zincirler.
41                supplyAsync: arka planda çalış+sonuç döndür; runAsync: sonuçsuz görev.
42                thenApply (dönüştür) / thenAccept (tüket) / thenCompose (sonuçla yeni async iş başlat).
43                join()/get(): sonucu bekle. Varsayılan ForkJoinPool.commonPool'da çalışır (executor verilebilir).""");
44    }
45
46    static void yavasIs(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ignored) {} }
47    static CompletableFuture<String> profilGetir(String id) {
48        return CompletableFuture.supplyAsync(() -> { yavasIs(100); return "Profil(" + id + ")"; });
49    }
50}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: CompletableFuture — paralel birleştirme (thenCombine, allOf) ve hata yönetimi.
2// Çalıştırma: java Ornek2.java
3import java.util.List;
4import java.util.concurrent.CompletableFuture;
5import java.util.stream.Collectors;
6
7public class Ornek2 {
8
9    public static void main(String[] args) {
10        long t0 = System.currentTimeMillis();
11
12        // thenCombine: İKİ bağımsız asenkron işi PARALEL çalıştır, sonuçlarını birleştir.
13        CompletableFuture<Integer> fiyat = CompletableFuture.supplyAsync(() -> { uyu(200); return 100; });
14        CompletableFuture<Integer> adet = CompletableFuture.supplyAsync(() -> { uyu(200); return 3; });
15        CompletableFuture<Integer> toplam = fiyat.thenCombine(adet, (f, a) -> f * a);
16        System.out.println("thenCombine (paralel) toplam: " + toplam.join()
17                + "  (~" + (System.currentTimeMillis() - t0) + " ms, ~200 ms çünkü PARALEL)");
18
19        // allOf: birden çok işi başlat, HEPSİNİN bitmesini bekle.
20        List<CompletableFuture<String>> isler = List.of(
21                CompletableFuture.supplyAsync(() -> { uyu(100); return "A"; }),
22                CompletableFuture.supplyAsync(() -> { uyu(150); return "B"; }),
23                CompletableFuture.supplyAsync(() -> { uyu(120); return "C"; }));
24        CompletableFuture.allOf(isler.toArray(new CompletableFuture[0])).join();
25        String hepsi = isler.stream().map(CompletableFuture::join).collect(Collectors.joining(", "));
26        System.out.println("allOf sonuçları: " + hepsi);
27
28        // exceptionally: hata olursa yedek değer döndür (asenkron try-catch gibi).
29        CompletableFuture<Integer> hatali = CompletableFuture
30                .supplyAsync(() -> { if (true) throw new RuntimeException("servis çöktü"); return 1; })
31                .exceptionally(ex -> {
32                    System.out.println("exceptionally: hata yakalandı -> " + ex.getMessage());
33                    return -1;   // yedek değer
34                });
35        System.out.println("hata sonrası değer: " + hatali.join());
36
37        System.out.println("""
38
39                --- Birleştirme ve hata yönetimi ---
40                thenCombine(diger, fn): iki bağımsız işi PARALEL çalıştırıp sonuçları birleştirir (toplam süre = en uzun iş).
41                allOf(...): birçok işi başlat, hepsinin bitmesini bekle (anyOf: ilk biteni bekler).
42                exceptionally(fn): zincirde hata olursa yedek değer üret (handle/whenComplete de var).
43                Faydası: G/Ç ağırlıklı işleri (API çağrıları, DB) PARALEL yürütüp toplam süreyi düşürmek.""");
44    }
45
46    static void uyu(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ignored) {} }
47}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.