Reaktif (Reactive) ve Asenkron (Asynchronous) Programlama

Yazılım geliştiricilerde sıklılıkla bir birine karıştığını düşündüğüm iki yaklaşım var: Reaktif (Reactive) ve Asenkron (Asynchronous) programlama. Bu terimler, yazılım geliştirme yöntemi olarak tamamen farklı iki yaklaşımdır.

“Reactive” (tepki verici, tepkisel), bir sistemin belirli bir girdiye verdiği tepkiyi ifade eder. Bu tepki, girdiyi işleme ve sonucunu geri döndürme şeklinde olabilir. Reactive sistemler, girdiye hızlı tepki vermeye ve yüksek performans sunmaya özen gösterirler.

“Asynchronous” (eşzamansız), bir sistemin birden fazla görevi aynı anda gerçekleştirebildiği anlamına gelir. Bu, bir görevin gerçekleşmesi için diğer görevlerin beklemesine gerek kalmaksızın sistemin hala çalışabildiği anlamına gelir. Asynchronous sistemler, görevler arasında eşit paydaşlık sağlamaya ve görevlerin sıraya göre gerçekleştirilmesine gerek kalmaksızın paralel olarak gerçekleştirilmesine özen gösterir. Birden fazla işlemi aynı anda yürüterek işlemler arasındaki bekleme süresini azaltmayı hedefler. Bu işlemler arasında Java dünyasında Thread ve Executor kullanılabilir.

Dolayısıyla, bir sistem hem reactive hem de asynchronous olabilir. Bir girdiye hızlı bir tepki vererek ve birden fazla görevi aynı anda gerçekleştirerek yüksek performans sunabilir. Ancak, sistem sadece reactive veya sadece asynchronous olabilir de.

Örnekleyelim

Reaktif (Reactive) Programlama

Aşağıdaki örnek Java kodu, RxJava kütüphanesini kullanarak bir giriş noktasından okunan bir veriyi asenkron bir şekilde işleyen ve sonucunu ekrana yazan bir uygulamadır.

import io.reactivex.rxjava3.core.Observable;

public class Main {
 public static void main(String[] args) {
   Observable<String> source = Observable.just("Hello", "Reactive",   "Programming");

  source
   .subscribeOn(Schedulers.io())
   .observeOn(Schedulers.single())
   .subscribe(System.out::println);

  System.out.println("Finished");
 }
}

İlk olarak, Observable.just() metodu ile bir stream oluşturulur. Bu metod, verilen parametreleri bir stream olarak saklar ve bu stream üzerinde işlem yapmaya olanak sağlar. Örneğin bu kodda, “Hello”, “Reactive”, “Programming” stringleri bir stream olarak saklandı.

Daha sonra subscribeOn() methodu ile stream işlemleri için bir Thread belirlendi. Bu method, stream işlemlerinin nerede gerçekleştirileceğini belirler. Schedulers.io() kullanımı ile stream işlemleri bir IO işlemi olarak gerçekleştirilecektir.

observeOn() methodu ise, stream işlemlerinin sonucunun nerede kullanılacağını belirler. Schedulers.single() kullanımı ile stream işlemlerinin sonucunun tek bir Thread üzerinde kullanılacağı belirlenir.

Son olarak subscribe() metodu ile stream işlemleri sonunda yapılacak işlem belirlendi. Bu kodda System.out::println kullanılarak stream işlemlerinin sonucu ekrana yazdırılmaktadır.

Son olarak System.out.println("Finished"); komutu çalıştırıldığında yazdırır ve programın çalışması tamamlanır.

Asenkron (Asynchronous) Programlama

Bu örnek, Executor ile Thread kullanarak asenkron bir şekilde bir işlemi gerçekleştirir.

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        Executor executor = Executors.newSingleThreadExecutor();
        Runnable task = () -> {
            System.out.println("Asynchronous task is running...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Asynchronous task is finished");
        };
        executor.execute(task);
        System.out.println("Main thread is finished");
    }
}

Bu kod, Executor kullanarak bir Runnable task oluşturuldu ve execute metodu ile task’ı gerçekleştirildi, bu task Thread.sleep ile 1 saniye bekledi ve sonrasında “Asynchronous task is finished” yazdırıldı. System.out.println("Main thread is finished"); komutu çalıştırıldığında yazdırır ve programın çalışması tamamlandı. Ancak bu task gerçekleşirken main thread çalışmaya devam eder.

Bu örnekte Executor, Executors.newSingleThreadExecutor() ile oluşturuldu, bu yüzden sadece bir Thread üzerinde işlem gerçekleştirilir. Bu, işlemler arasında seri bir şekilde çalışmasını sağlar. Ayrıca başka Executor tipleri de kullanılabilir, örneğin, Executors.newFixedThreadPool() ile birden fazla Thread üzerinde işlem gerçekleştirmek mümkündür.