Java’da CompletableFuture ile Asenkron Programlama

Java’da asenkron programlama, özellikle büyük veri işleme veya uzun süren işlemleri gerçekleştirmek için oldukça kullanışlıdır. Bu sayede, bir uygulamanın uzun süren işlemler sırasında diğer işlemleri de yürütebilmesi sağlanır. Java 8 ile birlikte sunulan CompletableFuture, asenkron işlemleri yönetmek için güçlü bir araçtır. Bu yazıda, CompletableFuture‘ün ne olduğunu, nasıl kullanıldığını ve Java’da asenkron iş akışlarını yönetmenin avantajlarını detaylı bir şekilde ele alacağız.

CompletableFuture Nedir?

CompletableFuture, Java’da asenkron işlemleri gerçekleştirmek ve yönetmek için kullanılan bir sınıftır. CompletableFuture, bir sonucu gelecek bir tarihte tamamlanabilecek bir işlemi temsil eder ve bu sayede asenkron iş akışlarını kolaylıkla oluşturmanızı sağlar. Bu araç, özellikle paralel ve birbirine bağımlı işlemleri zincirleme ve yönetme konusunda büyük bir esneklik sunar.

CompletableFuture ile Temel Asenkron İşlem

CompletableFuture kullanarak bir işlemi asenkron olarak başlatmak oldukça basittir. Aşağıdaki örnekte, asenkron olarak bir hesaplama işlemi yapacağız.

Örnek: CompletableFuture Kullanımı

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000); // 2 saniyelik bekleme simülasyonu
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            return "İşlem Tamamlandı";
        });

        System.out.println("Asenkron işlem başlatıldı...");
        try {
            String result = future.get(); // İşlemin tamamlanmasını bekle ve sonucu al
            System.out.println(result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Bu örnekte, supplyAsync() metodu ile asenkron bir işlem başlatılıyor ve sonuç daha sonra get() metodu ile alınıyor. Bu sayede diğer işlemler beklemeden devam edebilir.

CompletableFuture Metotları

CompletableFuture, zincirleme işlemler ve asenkron işlemleri kolayca yönetebilmeniz için çeşitli metotlar sunar. Bunlardan bazıları şunlardır:

  • thenApply(): Bir işlem tamamlandıktan sonra, sonucunu başka bir işleme dönüştürmek için kullanılır.
  • thenAccept(): İşlem tamamlandıktan sonra sonucu kullanarak bir işlem yapmak için kullanılır. Geri dönüş değeri yoktur.
  • thenRun(): İşlem tamamlandıktan sonra, sonucu kullanmadan bir işlem yapmak için kullanılır.
  • thenCombine(): İki farklı CompletableFuture sonucunu birleştirip tek bir sonuç üretmek için kullanılır.
  • thenCompose(): Bir CompletableFuture‘ün sonucuna dayalı olarak yeni bir CompletableFuture oluşturmak için kullanılır.
thenApply() ve thenAccept() Kullanımı

Bir işlem tamamlandıktan sonra yeni bir işlem yapmak istiyorsanız thenApply() veya thenAccept() metodunu kullanabilirsiniz.

Örnek: thenApply() ve thenAccept() Kullanımı

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 50)
                .thenApply(num -> num * 2) // 50'yi iki ile çarp
                .thenApply(result -> result + 10); // Sonuca 10 ekle

        try {
            System.out.println("Sonuç: " + future.get()); // Sonuç: 110
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        CompletableFuture<Void> future2 = CompletableFuture.supplyAsync(() -> "Merhaba, Dünya!")
                .thenAccept(System.out::println); // Sonucu yazdır

        try {
            future2.get(); // Sonucu al ve yazdır
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Bu örnekte, thenApply() kullanarak bir değeri iki ile çarptık ve ardından sonuca 10 ekledik. thenAccept() ise sonucu yazdırmak için kullanılmıştır.

thenCombine() ile Birden Fazla CompletableFuture Birleştirme

Bazen iki veya daha fazla asenkron işlemin sonucunu birleştirmek gerekebilir. thenCombine() metodu bu tür durumlar için idealdir.

Örnek: thenCombine() Kullanımı

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 20);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 30);

        CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (num1, num2) -> num1 + num2);

        try {
            System.out.println("Birleştirilmiş Sonuç: " + combinedFuture.get()); // Birleştirilmiş Sonuç: 50
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Bu örnekte, future1 ve future2 adlı iki farklı CompletableFuture‘ün sonucunu birleştirerek toplamını elde ettik.

Asenkron Hatalar ve Exception Handling

CompletableFuture kullanırken, asenkron işlemler sırasında oluşabilecek hataları ele almak önemlidir. exceptionally() metodu, bir CompletableFuture başarısız olduğunda hatayı ele almanıza olanak tanır.

Örnek: exceptionally() Kullanımı

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("Bir hata oluştu!");
            }
            return 42;
        }).exceptionally(ex -> {
            System.out.println("Hata: " + ex.getMessage());
            return 0;
        });

        try {
            System.out.println("Sonuç: " + future.get()); // Hata: Bir hata oluştu! Sonuç: 0
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Bu örnekte, supplyAsync() içinde bir istisna oluştuğunda exceptionally() metodu bu hatayı ele alır ve varsayılan bir değer döner.

CompletableFuture ve Paralel İşlemler

CompletableFuture, aynı anda birden fazla işlemi başlatmanıza ve bu işlemleri bağımsız olarak çalıştırmanıza olanak tanır. Bu tür bir kullanım, büyük miktarda veri işleme veya uzun süren işlemler için oldukça faydalıdır.

Örnek: Paralel Asenkron İşlemler

import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) {
        List<CompletableFuture<String>> futures = List.of(
                CompletableFuture.supplyAsync(() -> "İşlem 1 tamamlandı"),
                CompletableFuture.supplyAsync(() -> "İşlem 2 tamamlandı"),
                CompletableFuture.supplyAsync(() -> "İşlem 3 tamamlandı")
        );

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

        try {
            allOf.get(); // Tüm işlemler tamamlanana kadar bekle
            futures.forEach(future -> {
                try {
                    System.out.println(future.get());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            });
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Bu örnekte, CompletableFuture.allOf() metodu ile birden fazla asenkron işlemin tamamlanmasını bekledik ve ardından sonuçları yazdırdık.


Bu makalede, Java’da CompletableFuture kullanarak asenkron programlama ve zincirleme işlemleri nasıl yöneteceğimizi detaylı bir şekilde ele aldık. CompletableFuture, Java geliştiricileri için güçlü bir araçtır ve asenkron işlemleri yönetmeyi oldukça kolay hale getirir.