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 —flatMapgibi).join()/get(): Sonucun gelmesini bekler (getchecked exception atar).
Örnek 1 (./Ornek1.java) supplyAsync → thenApply → thenCompose → thenAccept 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
@AsyncmetotlarıCompletableFuturedö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ılabilir1// 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}Ornek2
çalıştırılabilir1// 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}