Sitemap
Ömer Çelik

Ömer Çelik

Java 8 — CompletableFuture

5 min readJan 17, 2020

Şimdi ise asynchronous işlemler için kullanılan CompletableFuture nesnesini inceleyeceğiz. Oldukça detaylı ve bol örnekli bir şekilde ele alacağım ve aklınıza takılan bir şey olacağını hiç sanmıyorum. Bölümün sonunda da güzel bir örnek yapacağım. Aynı şekilde o yaptığım örneğin github linkini de paylaşıcam.

Bundan önceki makalemde de yine asynchronous işlemler için kullanılan @Async annotationunu incelemiştim. Onu da okumanızı tavsiye ederim.

https://medium.com/@omercelikceng/async-in-spring-e86173f61b56

CompletableFuture Javadaki asynchronous programming için kullanılır.
Async programlama sayesinde application’ın main thread’inden ayrı bir thread çalıştırarak main threadin ilerlemesi, tamamlanması vb. durumlarını blocklamadan kod yazabiliriz.

Bu sayede Main threadimiz async yaptığımız taskın tamamlanmasını engellemez / beklemez ve diğer taskları paralel olarak yürütebilir.

CompletableFuture Java 5' te gelen Future’in extensionudur. Future nesnesi sayesinde asynchronous computation(hesaplama, işlem) yapabiliyoruz.
Ancak bu computation’leri combine etmek veya olası error’ları handle etmenin herhangi bir implementasyonu yoktu. Java 8 CompletableFuture, Future nesnesinin bu gibi bir çok eksikliğini tamamlamak için ortaya çıkmıştır.
Daha sonra Java 9 ‘da CompletableFuture’a daha da özellikler eklenmiştir. Ancak bu makale de ele alamayacağım.

En basit ve sade bir şekilde completableFuture nesnesi aşağıdaki gibi oluşur.

CompletableFuture - get() Method

Bir completableFuture ‘ın sonucunu almak için get() kullanabiliriz. get() methodu future tamamlanana kadar o threadi blocklar.

CompletableFuture - complete() Method

Future’i elle (manuel olarak) tamamlamak için ise CompletableFuture.complete() kullanabiliriz.

Buna niye ihtiyacımız olur? Mesela bir service’den bir bilgiyi almak istiyoruz.
Örneğin; TCMB apisi üzerinden dolar döviz kurunu almak istiyoruz. Ancak bu service’in down durumda olduğu case olabilir.
Bu durumda biz elimizle(manuel olarak) cache’imizde bulunan son döviz kurunu dönmek isteyebiliriz.

Böyle bir case’i handle etmek istersek bu method imdadımıza yetişir.

CompletableFuture - runAsync() Method

Bazı background taskları asynchronous olarak çalıştırmak ve task tamamlandıktan sonra herhangi bir şey döndürmek istemiyorsanız CompletableFuture.runAsync () methodunu kullanabilirsiniz.
Callback function olarak bir Runnable object parametresi alır ve CompletableFuture <Void> değerini döndürür.

Yada aynı methodun arrow function’lı hali :

CompletableFuture - supplyAsync() Method

CompletableFuture runAsync’den tek farkı bir şey return etmesidir.

GENEL ÖNEMLİ BİR NOT :
CompletableFuture, taskları global ForkJoinPool.commonPool() nesnesinden elde edilen bir thread’de yürütür.
Ancak isteğe bağlı olarak ayrıca kendimizin configure ettiği bir Thread Pool oluşturabilir ve taskları bu Thread Pool’dan alınan bir thread’den yürütmek için runAsync () ve supplyAsync() yöntemlerine iletebilirsiniz.

Aşağıda gördüğümüz gibi bu methodların ayrıca Executor parametresi aldığı çeşitleri de vardır:

ÖRNEK:

CompletableFuture - thenApply (), thenAccept () ve thenRun Methodları

CompletableFuture.get () yöntemi blocking’dir. Future tamamlanana kadar bekler ve tamamlanmasından sonra sonucu döndürür.
Ama istediğimiz bu değilse nolacak?
Async sistemler oluşturmak için CompletableFuture’a Future tamamlandığında otomatik olarak çağrılması gereken bir callback funtion ekleyebilmeliyiz. Bu şekilde, sonucu beklememiz gerekmeyecek ve
Future’in tamamlanmasından sonra callback fonksiyonumuz içinde yürütülmesi gereken logici yazabiliriz.

thenApply (), thenAccept () ve thenRun () yöntemlerini kullanarak CompletableFuture nesnesine callback function pass edebiliriz.

  1. CompletableFuture - thenApply() Methodu
    CompletableFuture sonucu geldiğinde process etmek ve transform etmek için thenApply() methodunu kullanabilirsiniz.
    thenApply ile future’dan gelen değeri alırız ilgili işlemlerimizi yapıp tekrar return edebiliriz.

Yada daha güzel bir biçimde de yazabiliriz.

2. CompletableFuture - thenAccept() and thenRun()
Bir önce anlatmış olduğumuz thenApply() methodunda callback function’ımız bir şey return eder.
Bizim ihtiyacımızda callback function’dan bir şey döndürmek istemiyorsanız
ve yalnızca Future tamamlandıktan sonra bir parça kod çalıştırmak istiyorsak
thenAccept() and thenRun() methodlarını kullanabilirsiniz.

CompletableFuture - thenAccept Methodu
CompletableFuture.thenAccept ()
bir Consumer<T> alır ve CompletableFuture<Void> değerini return eder.
thenAccept methodu bağlı olduğu CompletableFuture sonucuna erişebilir.

Örnek :

Örnekte de gördüğümüz gibi supplyAsync methodu tamamlandıktan sonra return edilen değeri yakalayıp onun üzerinde logic yapabildik.

CompletableFuture - thenRun Methodu

thenRun methodunun thenAccept’den farkı ise bağlı olduğu CompletableFuture’ ın sonucuna istese de erişemez.

NOT : thenRun, thenAccept, thenApply gibi callback function’ların ayrıca
asynchronous olan methodları da vardır. Ne demek istiyorum? Bunlar zaten async çalışmıyor mu? Görelim..

Bu async callback variation’ları sayesinde, callback taskları ayrı bir thread’de yürüterek hesaplamalarınızı daha fazla paralelleştirmenize yardımcı olur.

Get Ömer Çelik’s stories in your inbox

Join Medium for free to get updates from this writer.

Şimdi bu durumu örnekle açıklayalım :

Yukarıdaki durumda, thenApply() içindeki task, supplyAsync() taskının yürütüldüğü aynı threadinde veya supplyAsync() taskı hemen tamamlanırsa main threadde yürütülür. Java bizim için 2 çeşitini de yapmış.

İhtiyacımıza göre callback taskını execute eden thread üzzerinde daha fazla kontrole sahip olmak isteyebiliriz. Bu durumda Java’nın bize sağlamış olduğu diğer varyasyon olan async callback’leri kullanmalıyız.
ThenApplyAsync () callback’ini kullanırsanız, bu
ForkJoinPool.commonPool () — nesnesinden alınan farklı bir threadde yürütülür.

Ayrıca, bir Executor’u thenApplyAsync () callback’ine argüman olarak iletirseniz, task Executor’un thread poolundan elde edilen bir thread içinde yürütülür.

Combine Operation in CompletableFuture

Şimdiye kadar CompletableFuture’un bize sağlamış olduğu bir çok methodu inceledik. Şimdi ise elde etmiş olduğumuz completableFuture’ları nasıl combine edebiliriz, nasıl birbirlerini beklemelerini sağlayabiliriz vb. methodları inceleyelim.

  1. CompletableFuture - thencompose() Method

Birbirine bağımlı olan 2 future’i birleştirmek için kullanılır.
Örneğin ; Bir user detail bilgisini uzak bir API servisinden almak istediğinizi
ve user detail bilgisi available olduğunda(yani elimize geldiğinde), o user’in credit rating’ini başka bir service’den almak istediğinizi varsayalım.
User bilgisini ve credit rating’i çeken methodlarımız aşağıdaki gibidir.
Peki bunları nasııl combine edicez?

Öncelikli olarak thenApply() methodunu kullanalım ve ne olacak görelim.

Sonuç elimize CompletableFuture<CompletableFuture<Double>> şeklinde geldi. İç içe bir completableFuture elde etmiş olduk.
Böyle iç içe completableFuture olmadan bir sonuç elde etmek istiyorsak thencompose() methodunu kullanmalıyız.

Yani, callback function bir CompletableFuture return ederse ve CompletableFuture zincirinden flat() bir result dönmesini istiyorsak thenCompose() ögesini kullanmalıyız.

2. CompletableFuture - thenCombine Method

thenCompose(), bir Future’ın diğerine bağımlı olduğu 2 Futures’ı birleştirmek için kullanılırken, iki Future’in bağımsız olarak çalışmasını ve her 2'side tamamlandıktan sonra bir şey yapılmasını istiyorsak thenCombine() methodunu kullanınız.

Her 2 future tamamlandığında thenCombine() methoduna pass edilen
callback function çağrılır.

Birden fazla CompletableFuture’ı bir araya getirmek (Combining multiple CompletableFutures)

Iki CompletableFuture’u bir araya getirmek için thenCompose() ve thenCombine() methodlarını kullandık.
Şimdi, rastgele sayıda CompletableFutures’ı birleştirmek isterseniz ne olacak? Herhangi bir sayıda CompletableFutures’ı birleştirmek için aşağıdaki yöntemleri kullanabilirsiniz.

  1. CompletableFuture - allOf() Method

CompletableFuture.allOf(), birbirinden bağımsız Future’larımızı paralel olarak çalıştırmak ve hepsi tamamlandıktan sonra bir şeyler yapıp ve bu bağımsız Future’larımızın sahip olduğu bir List’in var olduğunda senaryolarda kullanılır.

Örneğin; bir web sayfası linkini parametre olarak alan ve bir CompletableFuture döndüren bir method yazdığımızı varsayalım,
yani verdiğimiz linkin içeriğini async olarak indiren bir method.
O method aşağıdaki gibidir.
Aşağıdaki method async bi işlem yapıyor ve CompletableFuture<String> dönüyor.

O halde kodun geriye kalan kısmını yazalım.

Şu an tüm Future’lar bir Future’da bir araya toplandı.(CompletableFuture.allOf)
Tüm Futures’lar tamamlandığında sonuçlarını almak için `future.join() veya future.get` i kullanırız.
Bu arada join sonuçların hepsi tamamlanıncaya kadar bekletir ancak birleştirme yapmaz. Biz collectors.toList ile yapacağız.

2. CompletableFuture - anyOf() Method

Verdiğimiz CompletableFutures’lardan en önce hangisi tamamlandıysa aynı türde bir CompletableFuture döner.

CompletableFuture Exception Handling

Böyle bir kod blogunda supplyAsync() taskında bir hata oluşursa thenApply() callbacklerinin hiçbiri call edilmez ve exception oluştuğunda Future resolved olur.
İlk thenApply() callback’ inde bir exception oluşursa 2. ve 3. callbackler call edilmez ve exception oluştuğunda Future resolved olur.

  1. CompletableFuture - exceptionally() Method

exceptionally() callback size original Future’ten oluşturulan hatalardan kurtulma şansı verir. Exceptionı buraya kaydedebilir ve varsayılan bir değer döndürebilirsiniz.

Hatayı bir kere handle ederseniz, callback chain de daha fazla yayılmaz.

2. CompletableFuture - generic handle() Method

API, exceptionlardan kurtulmak için daha generic bir method olan handle () sağlar. Bir exception oluşursa, res argümanı null olur, aksi takdirde ex argümanı null olur.

Ayrıca github’da bu örneklerimden daha fazlasını yaptım. İndirip incelemek isterseniz ;

--

--