← Kursa Dön
📄 Text · 25 min

Senkron vs Asenkron Programlama

Giriş

Bir kafede kahve sipariş verdiğinizi düşünün. Senkron model: Kasada sipariş veriyorsunuz, barista kahvenizi hazırlıyor, siz kasanın önünde dikiliyor, kahve hazır olunca alıp gidiyorsunuz. Arkanızdaki 20 kişi? Onlar da bekliyor. Asenkron model: Sipariş veriyorsunuz, bir numara alıyorsunuz, kenara çekiliyorsunuz. Barista kahvenizi hazırlarken kasadaki diğer müşteriler de sipariş verebiliyor. Numaranız çağrılınca kahvenizi alıyorsunuz.

Modern yazılım geliştirmede performans ve kullanıcı deneyimi en kritik metriklerin başında gelir. Bir e-ticaret uygulaması düşünün: kullanıcı sipariş verdiğinde ödeme işlemi, stok kontrolü, e-posta gönderimi, fatura oluşturma ve bildirim gönderme gibi birçok adım gerçekleşir. Bu adımları sırayla (senkron) çalıştırmak kullanıcıyı 7-10 saniye bekletebilir. Oysa bazı adımlar birbirinden bağımsızdır ve paralel çalışabilir — işte asenkron programlama bu noktada devreye girer.

Bu derste senkron ve asenkron programlama modellerini, blocking ve non-blocking I/O kavramlarını, thread modeli ve kaynak kullanımını, hangi senaryoda hangi modelin uygun olduğunu ve Spring Boot'un sunduğu asenkron araçları derinlemesine öğreneceğiz.


Senkron (Blocking) Model

Senkron programlamada her işlem sırayla gerçekleşir. Bir işlem tamamlanmadan diğerine geçilmez. Çalışan thread, işlem bitene kadar bloke olur ve başka hiçbir iş yapamaz. Buna blocking model denir.

Sipariş İşleme — Senkron Yaklaşım

@Service
public class OrderService {
    
    public OrderResult processOrder(Order order) {
        // Adım 1: Ödeme al (2 saniye)
        PaymentResult payment = paymentService.charge(order);
        
        // Adım 2: Stok ayır (1 saniye)
        InventoryResult stock = inventoryService.reserve(order);
        
        // Adım 3: Onay e-postası gönder (3 saniye)
        EmailResult email = emailService.sendConfirmation(order);
        
        // Adım 4: Fatura oluştur (1 saniye)
        InvoiceResult invoice = invoiceService.generate(order);
        
        // Toplam bekleme: 2 + 1 + 3 + 1 = 7 saniye!
        // Kullanıcı 7 saniye boyunca "loading" görüyor
        return new OrderResult(payment, stock, email, invoice);
    }
}

Thread zaman çizelgesi:

Thread-1: [===PAYMENT(2s)===][===STOCK(1s)===][===EMAIL(3s)===][===INVOICE(1s)===]
          0                  2               3                6                 7 saniye
                                                                              ↑ Yanıt

Senkron Modelin Avantajları

Senkron modelin basitliği en büyük gücüdür:

  • Anlaşılması kolay: Kod yukarıdan aşağıya sırayla okunur, akış nettir

  • Hata yönetimi basit: try-catch ile tüm hataları doğrusal olarak yakalarsınız

  • Debug kolay: Stack trace tam ve anlamlıdır

  • Transaction bütünlüğü: Tüm adımlar aynı thread'de, sıralı çalışır

Senkron Modelin Dezavantajları

Ancak ciddi sınırlamaları vardır:

  • Yavaş: Toplam süre = tüm adımların süreleri toplamı

  • Thread israfı: Thread, I/O beklerken CPU kullanmaz ama bellekte yer kaplar

  • Ölçeklenemezlik: Her eşzamanlı istek bir thread gerektirir, yüksek trafikte thread havuzu tükenir

  • Domino etkisi: Bir adım yavaşlarsa tüm işlem yavaşlar


Thread Modeli ve Kaynak Tüketimi

Java'da her thread belirli miktarda sistem kaynağı tüketir. Bu kavramı anlamak, senkron modelin neden ölçeklenmediğini kavramak için kritiktir.

Thread Bellek Maliyeti

Her Java thread'i:
  - Stack memory: 512 KB – 1 MB (varsayılan: -Xss512k)
  - OS thread: kernel yapıları için ek ~8-16 KB
  - Thread-local değişkenler: değişken

200 eşzamanlı istek → 200 thread:
  - Minimum bellek: 200 × 512 KB = 100 MB (sadece stack!)
  - Bu thread'lerin çoğu I/O beklerken HİÇBİR İŞ yapmıyor
  
1000 eşzamanlı istek → 1000 thread:
  - Minimum bellek: 1000 × 512 KB = 500 MB
  - Context switching overhead: CPU zamanının %20-30'u boşa gider

Thread Starvation (Açlığı)

Spring Boot'un varsayılan Tomcat thread pool'u 200 thread içerir. Her istek bir thread kullanır ve işlem süresince tutulur:

Senaryo: Her istek 7 saniye sürüyor (senkron sipariş işleme)

Thread kapasitesi: 200 thread
Her thread'in işleme süresi: 7 saniye
Throughput: 200 / 7 = ~28 istek/saniye

201. istek geldiğinde: Thread yok! → Bekleme kuyruğuna girer
Kuyruk da dolarsa: "Connection refused" → Kullanıcı hata görür

Black Friday'de 500 eşzamanlı istek:
  200 istek işleniyor, 300 istek bekliyor
  Ortalama bekleme süresi: 7+ saniye
  Bazı kullanıcılar: timeout → Sepeti terk ediyor → Gelir kaybı!

Asenkron (Non-Blocking) Model

Asenkron programlamada bir işlem başlatıldığında thread bloke olmaz. İşlem arka planda devam ederken thread başka işler yapabilir. İşlem tamamlandığında bir callback, Future veya event mekanizması ile sonuç alınır.

Sipariş İşleme — Asenkron Yaklaşım

@Service
public class AsyncOrderService {
    
    public CompletableFuture<OrderResult> processOrderAsync(Order order) {
        // Tüm işlemler AYNI ANDA başlatılır
        CompletableFuture<PaymentResult> paymentFuture =
            CompletableFuture.supplyAsync(() -> paymentService.charge(order));
        
        CompletableFuture<InventoryResult> stockFuture =
            CompletableFuture.supplyAsync(() -> inventoryService.reserve(order));
        
        CompletableFuture<EmailResult> emailFuture =
            CompletableFuture.supplyAsync(() -> emailService.sendConfirmation(order));
        
        CompletableFuture<InvoiceResult> invoiceFuture =
            CompletableFuture.supplyAsync(() -> invoiceService.generate(order));
        
        // Tüm işlemler paralel çalışır
        // Toplam süre: max(2, 1, 3, 1) = 3 saniye (en yavaş işlem kadar)
        return CompletableFuture.allOf(
                paymentFuture, stockFuture, emailFuture, invoiceFuture)
            .thenApply(v -> new OrderResult(
                paymentFuture.join(),
                stockFuture.join(),
                emailFuture.join(),
                invoiceFuture.join()
            ));
    }
}

Thread zaman çizelgesi:

Thread-1: [===PAYMENT(2s)===]
Thread-2: [=STOCK(1s)=]
Thread-3: [=====EMAIL(3s)=====]
Thread-4: [=INVOICE(1s)=]
          0                    3 saniye
                              ↑ Yanıt (en yavaş işlem kadar: 3 saniye)

Senkron: 7 saniye → Asenkron: 3 saniye → %57 daha hızlı!

Hibrit Yaklaşım: Kritik + Fire-and-Forget

Gerçek dünyada tüm işlemleri paralel yapmak her zaman doğru değildir. Bazı işlemler sıralı olmalıdır (önce ödeme, sonra stok), bazıları ise fire-and-forget (e-posta, bildirim):

@Service
public class SmartOrderService {
    
    private final NotificationService notificationService;
    private final ApplicationEventPublisher eventPublisher;
    
    public OrderResult processOrder(Order order) {
        // 1. KRİTİK — Sıralı, senkron (ödeme başarılı olmalı ki devam edelim)
        PaymentResult payment = paymentService.charge(order);
        
        // 2. KRİTİK — Sıralı, senkron (stok yoksa sipariş oluşturulamaz)
        InventoryResult stock = inventoryService.reserve(order);
        
        // 3. Sipariş kaydı (senkron — DB transaction)
        Order savedOrder = orderRepository.save(order);
        
        // 4. YAN ETKİLER — Asenkron, fire-and-forget
        // Bu işlemler arka planda çalışır, kullanıcıyı BEKLETMEZ
        notificationService.sendOrderConfirmation(savedOrder);  // @Async
        notificationService.notifyWarehouse(savedOrder);         // @Async
        eventPublisher.publishEvent(new OrderCreatedEvent(savedOrder)); // Event
        
        // Kullanıcıya hemen yanıt dön (2 + 1 = 3 saniye)
        // E-posta, bildirim, analitik arka planda devam eder
        return new OrderResult(payment, stock, savedOrder.getId());
    }
}

Blocking vs Non-Blocking I/O

Blocking I/O

Thread, I/O işlemi (disk okuma, ağ isteği, veritabanı sorgusu) tamamlanana kadar bekler. Bu sürede thread CPU kullanmaz ama bellekte yer kaplar ve başka iş yapamaz.

Blocking I/O — Thread yaşam döngüsü:

Thread: [CPU çalışıyor][----I/O BEKLİYOR----][CPU çalışıyor][----I/O BEKLİYOR----]
         ~%5 CPU           ~%95 bekleme         ~%5 CPU          ~%95 bekleme

Tipik bir web uygulamasında:
  - CPU çalışma: %5-10 (JSON parse, iş mantığı, serialization)
  - I/O bekleme: %90-95 (veritabanı, HTTP çağrısı, dosya okuma)
  
Thread'in zamanının %95'i HİÇBİR ŞEY YAPMADAN geçiyor!

Non-Blocking I/O

Thread, I/O isteğini gönderir ve hemen geri döner. İşlem tamamlandığında bir bildirim (callback, event) alır. Java NIO (New I/O) paketi bu modeli destekler.

Non-Blocking I/O — Thread yaşam döngüsü:

Thread: [İstek-1 gönder][İstek-2 gönder][İstek-3 gönder][İstek-1 yanıt][İstek-2 yanıt]...
        
Tek thread, birçok I/O işlemini aynı anda yönetebilir!
Bekleme yok → Thread sürekli çalışıyor → Kaynak verimliliği

Karşılaştırma Tablosu

ÖzellikBlocking I/ONon-Blocking I/O
Thread kullanımıİşlem başına bir threadAz sayıda thread ile çok işlem
Bellek kullanımıYüksek (her thread ~512KB-1MB)Düşük
Kodun karmaşıklığıBasit, sıralıCallback/Future ile daha karmaşık
ÖlçeklenebilirlikSınırlı (thread sayısı)Yüksek
Hata ayıklamaKolay (stack trace net)Zor (asenkron stack trace parçalı)
Throughput (yüksek yük)DüşükYüksek

Thread Pool Kavramı

Asenkron programlamada thread'leri doğrudan oluşturmak yerine thread pool (havuz) kullanılır. Thread oluşturma maliyetlidir (OS thread, stack allocation); pool ile thread'ler yeniden kullanılır.

// ❌ YANLIŞ — Her işlem için yeni thread (kaynak israfı)
public void processAsync() {
    new Thread(() -> doWork()).start();  // Her çağrıda yeni thread!
    new Thread(() -> doWork()).start();  // Kontrolsüz thread oluşturma
    new Thread(() -> doWork()).start();  // Bellek patlar, OS sınırına ulaşılır
}

// ✅ DOĞRU — Thread pool kullanımı
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);       // Her zaman hazır 5 thread
        executor.setMaxPoolSize(20);       // Yoğun dönemde 20'ye kadar
        executor.setQueueCapacity(100);    // 100 görev kuyruğa alınabilir
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

Thread pool mekanizması:

Görev geldi
     │
     ▼
[Core thread boş?] ── Evet ──→ Core thread çalıştırır
     │ Hayır
     ▼
[Kuyrukta yer var?] ── Evet ──→ Kuyruğa ekle, sırasını bekle
     │ Hayır
     ▼
[Max thread'e ulaşıldı?] ── Hayır ──→ Yeni thread oluştur
     │ Evet
     ▼
Rejection Policy (hata fırlat / çağıranı yavaşlat / görevi at)

Ne Zaman Asenkron Kullanmalı?

Asenkron programlama her senaryoda doğru seçim değildir. Yanlış yerde kullanmak kodu karmaşıklaştırır ve hata ayıklamayı zorlaştırır. İşte karar vermenizi sağlayacak rehber:

✅ Asenkron Kullanın

1. Bağımsız I/O işlemleri paralel çalışabiliyorsa:
   → E-posta + push bildirim + SMS aynı anda gönderilebilir
   → 3 farklı servisten veri aynı anda çekilebilir

2. Uzun süren işlemler kullanıcıyı bekletmemeli:
   → Rapor oluşturma: "Rapor hazırlanıyor, hazır olunca e-posta atacağız"
   → Dosya işleme: Video transcode, resim optimize

3. Fire-and-forget senaryoları:
   → Audit log yazma
   → Analitik event gönderme
   → Cache invalidation

4. Yüksek eşzamanlılık (high concurrency):
   → 10.000+ eşzamanlı kullanıcı
   → WebSocket bağlantıları
   → Real-time notification

❌ Senkron Kalın

1. İşlemler arasında sıralı bağımlılık varsa:
   → Önce ödeme al → sonra stok ayır → sonra kargo başlat
   → Her adım bir öncekinin SONUCUNA bağlı

2. CPU-bound (yoğun hesaplama) işlemleri:
   → Asenkron, I/O bekleme sürelerini optimize eder
   → CPU işlemleri zaten thread'i meşgul tutar
   → CPU-bound için parallelStream() veya ForkJoinPool tercih edin

3. Basitlik kritikse ve performans yeterliyse:
   → 100ms'de biten bir istek için asenkron karmaşıklık gereksiz
   → "Premature optimization is the root of all evil" — Knuth

4. Transaction bütünlüğü gerekiyorsa:
   → Tek veritabanı transaction'ı senkron çalışmalı
   → @Transactional + @Async birlikte DİKKATLİ kullanılmalı
   → Asenkron metot kendi transaction'ını açar

5. Hata yönetimi kritikse:
   → Senkron: try-catch basit ve güvenilir
   → Asenkron: Exception'lar kaybolabilir, CompletableFuture chain'leri karmaşık

Karar Matrisi

                    Bağımsız mı?
                    /          \
                 Evet          Hayır
                /                \
         I/O ağırlıklı?     → SENKRON
           /        \
        Evet        Hayır (CPU)
         |            |
     ASENKRON    parallelStream
     (CompletableFuture,    (veya ForkJoinPool)
      @Async, Event)

Spring Boot'ta Asenkron Araçlar

Spring Boot, asenkron programlama için birden fazla seviyede araç sunar. Her biri farklı senaryolar için idealdir:

1. @Async Anotasyonu

En basit yöntem — metodu ayrı bir thread'de çalıştırır:

@Service
public class NotificationService {
    
    @Async
    public void sendEmail(String to, String subject) {
        // Bu metot ayrı thread'de çalışır
        emailClient.send(to, subject);
    }
    
    @Async
    public CompletableFuture<Report> generateReport(String type) {
        Report report = reportGenerator.create(type);
        return CompletableFuture.completedFuture(report);
    }
}

Kullanım: Fire-and-forget görevler, basit paralel çalıştırma.

2. CompletableFuture

Java 8+ ile gelen güçlü asenkron API — zincirleme, birleştirme, hata yönetimi:

CompletableFuture.supplyAsync(() -> fetchUser(userId))
    .thenCompose(user -> fetchOrders(user.getId()))
    .thenApply(orders -> calculateTotal(orders))
    .exceptionally(ex -> {
        log.error("Hata", ex);
        return BigDecimal.ZERO;
    });

Kullanım: Birbirine bağlı asenkron işlem zincirleri, birden fazla kaynağı birleştirme.

3. ThreadPoolTaskExecutor

Thread pool yönetimi ve konfigürasyonu:

@Bean
public TaskExecutor emailExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(3);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(50);
    executor.setThreadNamePrefix("email-");
    return executor;
}

Kullanım: Farklı iş yükleri için ayrı thread pool'lar.

4. @Scheduled

Periyodik görevler — cron, fixedRate, fixedDelay:

@Scheduled(cron = "0 0 2 * * *")  // Her gece 02:00
public void cleanupExpiredSessions() {
    sessionRepository.deleteExpired();
}

Kullanım: Zamanlanmış görevler — temizlik, rapor, senkronizasyon.

5. ApplicationEvent

Olay tabanlı gevşek bağlı (loosely coupled) iletişim:

// Olay yayınla
eventPublisher.publishEvent(new OrderCreatedEvent(order));

// Olay dinle — başka bir sınıfta
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
    analyticsService.trackOrder(event.getOrder());
}

Kullanım: Modüler mimari, yan etkilerin (e-posta, bildirim, loglama) ana iş mantığından ayrılması.


Performans Karşılaştırması

Aynı senaryoyu senkron ve asenkron modellerle karşılaştıralım:

Senaryo: E-ticaret uygulaması, 1000 eşzamanlı kullanıcı
Her sipariş: ödeme(2s) + stok(1s) + e-posta(3s) + fatura(1s)

SENKRON MODEL:
  İstek süresi: 7 saniye
  Thread pool: 200 thread
  Throughput: 200 / 7 = ~28 istek/saniye
  1000 kullanıcı: 1000 / 28 = ~36 saniye (son kullanıcı bu kadar bekler)
  
ASENKRON MODEL (paralel):
  İstek süresi: 3 saniye (en yavaş adım)
  Thread pool: 200 thread  
  Throughput: 200 / 3 = ~67 istek/saniye
  1000 kullanıcı: 1000 / 67 = ~15 saniye

HİBRİT MODEL (kritik senkron + fire-and-forget):
  İstek süresi: 3 saniye (ödeme + stok) — e-posta/fatura arka planda
  Thread pool: 200 thread
  Throughput: 200 / 3 = ~67 istek/saniye
  Kullanıcı deneyimi: 3 saniye bekleme (en iyi!)

Yaygın Hatalar

1. Her Şeyi Asenkron Yapmak

// ❌ YANLIŞ — Gereksiz karmaşıklık
@Async
public CompletableFuture<User> findById(Long id) {
    return CompletableFuture.completedFuture(
        userRepository.findById(id).orElseThrow()
    );
    // 1ms'lik bir sorgu için asenkron yapmanın anlamı yok!
}

// ✅ DOĞRU — Basit tutun
public User findById(Long id) {
    return userRepository.findById(id).orElseThrow();
}

2. Sıralı Bağımlı İşlemleri Paralel Yapmaya Çalışmak

// ❌ YANLIŞ — Ödeme başarılı olmadan stok ayırmamalısınız
CompletableFuture<PaymentResult> payment = payAsync(order);
CompletableFuture<InventoryResult> stock = reserveAsync(order); // Ödeme daha bitmedi!
// Ödeme başarısız olursa stok boşuna ayrılmış olur

// ✅ DOĞRU — Önce ödeme, sonra paralel yan etkiler
PaymentResult payment = paymentService.charge(order); // Senkron — bekle!
if (payment.isSuccessful()) {
    CompletableFuture.allOf(
        reserveStockAsync(order),
        sendEmailAsync(order),
        createInvoiceAsync(order)
    );
}

3. Exception'ları Kaybetmek

// ❌ YANLIŞ — void @Async'te exception sessizce kaybolur
@Async
public void sendEmail(String to, String body) {
    emailClient.send(to, body); // Exception fırlatırsa KİMSE BİLMEZ
}

// ✅ DOĞRU — Exception handler tanımlayın
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, params) -> {
            log.error("Async exception in {}: {}", 
                method.getName(), throwable.getMessage(), throwable);
            // Alert gönder, metrik kaydet
        };
    }
}

Gerçek Dünya Örneği: Dashboard Aggregation

Bir kullanıcı dashboard'u düşünün. Profil bilgisi, siparişler, cüzdan bakiyesi ve bildirimler ayrı servislerden gelir:

@RestController
@RequiredArgsConstructor
public class DashboardController {
    
    private final UserService userService;
    private final OrderService orderService;
    private final WalletService walletService;
    private final NotificationService notificationService;
    
    // ❌ SENKRON — Her servis sırayla çağrılır: 200+150+100+80 = 530ms
    @GetMapping("/dashboard/sync/{userId}")
    public DashboardResponse getDashboardSync(@PathVariable Long userId) {
        UserProfile profile = userService.getProfile(userId);         // 200ms
        List<Order> orders = orderService.getRecent(userId);          // 150ms
        WalletInfo wallet = walletService.getBalance(userId);         // 100ms
        List<Notification> notifs = notificationService.getUnread(userId); // 80ms
        
        return new DashboardResponse(profile, orders, wallet, notifs);
    }
    
    // ✅ ASENKRON — Paralel çağrı: max(200,150,100,80) = 200ms
    @GetMapping("/dashboard/async/{userId}")
    public DashboardResponse getDashboardAsync(@PathVariable Long userId) {
        CompletableFuture<UserProfile> profileFuture = 
            CompletableFuture.supplyAsync(() -> userService.getProfile(userId));
        CompletableFuture<List<Order>> ordersFuture = 
            CompletableFuture.supplyAsync(() -> orderService.getRecent(userId));
        CompletableFuture<WalletInfo> walletFuture = 
            CompletableFuture.supplyAsync(() -> walletService.getBalance(userId));
        CompletableFuture<List<Notification>> notifsFuture = 
            CompletableFuture.supplyAsync(() -> notificationService.getUnread(userId));
        
        CompletableFuture.allOf(profileFuture, ordersFuture, walletFuture, notifsFuture)
            .join();
        
        return new DashboardResponse(
            profileFuture.join(),
            ordersFuture.join(),
            walletFuture.join(),
            notifsFuture.join()
        );
    }
    
    // ✅✅ ASENKRON + HATA YÖNETİMİ — Bir servis başarısız olursa diğerleri etkilenmez
    @GetMapping("/dashboard/resilient/{userId}")
    public DashboardResponse getDashboardResilient(@PathVariable Long userId) {
        CompletableFuture<UserProfile> profileFuture = 
            CompletableFuture.supplyAsync(() -> userService.getProfile(userId))
                .orTimeout(2, TimeUnit.SECONDS)
                .exceptionally(ex -> UserProfile.defaultProfile(userId));
        
        CompletableFuture<List<Order>> ordersFuture = 
            CompletableFuture.supplyAsync(() -> orderService.getRecent(userId))
                .orTimeout(2, TimeUnit.SECONDS)
                .exceptionally(ex -> List.of());
        
        CompletableFuture<WalletInfo> walletFuture = 
            CompletableFuture.supplyAsync(() -> walletService.getBalance(userId))
                .orTimeout(2, TimeUnit.SECONDS)
                .exceptionally(ex -> WalletInfo.unavailable());
        
        CompletableFuture<List<Notification>> notifsFuture = 
            CompletableFuture.supplyAsync(() -> notificationService.getUnread(userId))
                .orTimeout(2, TimeUnit.SECONDS)
                .exceptionally(ex -> List.of());
        
        CompletableFuture.allOf(profileFuture, ordersFuture, walletFuture, notifsFuture)
            .join();
        
        return new DashboardResponse(
            profileFuture.join(),
            ordersFuture.join(),
            walletFuture.join(),
            notifsFuture.join()
        );
    }
}

Sonuç: 530ms → 200ms → %62 hızlanma, üstelik bir servis çökse bile dashboard kısmen gösterilir.


Özet

  • Senkron programlama basit, anlaşılır ve hata ayıklaması kolaydır. İşlemler sırayla çalışır, toplam süre tüm adımların toplamıdır. Thread, I/O beklerken hiçbir iş yapmaz.

  • Asenkron programlama, bağımsız işlemleri paralel çalıştırarak toplam süreyi en yavaş işlem kadar düşürür. Thread'ler verimli kullanılır, yüksek eşzamanlılık sağlanır.

  • Her şeyi asenkron yapmayın — sıralı bağımlılık varsa senkron kalın. CPU-bound işlemler için parallelStream tercih edin. Basit, hızlı işlemler için asenkron karmaşıklık gereksizdir.

  • Hibrit yaklaşım çoğu zaman en iyisidir: kritik iş mantığı senkron, yan etkiler (e-posta, bildirim, loglama) asenkron.

  • Spring Boot 5 temel asenkron araç sunar: @Async, CompletableFuture, ThreadPoolTaskExecutor, @Scheduled, ApplicationEvent. Her biri farklı senaryolar için idealdir.

  • Asenkron kodda exception handling kritiktir — void metotlarda exception sessizce kaybolur. AsyncUncaughtExceptionHandler veya CompletableFuture.exceptionally() kullanın.