Java İle Scheduledexecutor Service Kullanarak Zamanlanmış Görevler Nasıl Yapılır?

Java İle Scheduledexecutor Service Kullanarak Zamanlanmış Görevler Nasıl Yapılır?
Java İle Scheduledexecutor Service Kullanarak Zamanlanmış Görevler Nasıl Yapılır?

Java İle ScheduledExecutorService Kullanarak Zamanlanmış Görevler Nasıl Yapılır?

Java ekosisteminde arka plan görevlerini yönetmek, uygulama performansını korumak ve belirli aralıklarla tekrarlayan işlemleri otomatize etmek için en güvenilir yöntemlerden biri ScheduledExecutorService arayüzüdür. Geleneksel Timer sınıfının aksine, bu modern yapı çoklu iş parçacığı (multi-threading) desteği sunar ve hata yönetimi konusunda çok daha esnek bir yapıya sahiptir. Bu rehberde, Java ile ScheduledExecutorService kullanarak zamanlanmış görevler nasıl yapılır sorusuna adım adım yanıt bulacaksınız.

Bu makale, Java uygulamalarında belirli bir gecikmeden sonra veya düzenli aralıklarla görev çalıştırmak isteyen yazılım geliştiriciler için hazırlanmıştır. Öğrenme süreciniz boyunca, iş parçacığı havuzlarının (thread pool) nasıl yönetildiğini, görevlerin nasıl planlandığını ve üretim ortamında dikkat etmeniz gereken kritik noktaları uygulamalı kod örnekleriyle inceleyeceğiz.

Gereksinimler ve Ön Hazırlık

Bu rehberdeki örnekleri uygulayabilmek için sisteminizde Java Development Kit (JDK) 17 veya daha güncel bir sürümün yüklü olması önerilir. 2026 yılı standartlarında, modern Java uygulamaları modüler yapıları ve geliştirilmiş eşzamanlılık (concurrency) kütüphanelerini temel alır.

  • JDK 17+: Eşzamanlılık API'larındaki iyileştirmelerden yararlanmak için gereklidir.
  • IDE: IntelliJ IDEA, Eclipse veya VS Code gibi Java destekli bir geliştirme ortamı.
  • Temel Bilgi: Java'da Runnable arayüzü ve temel iş parçacığı (thread) mantığına aşinalık.

ScheduledExecutorService Temelleri

ScheduledExecutorService, java.util.concurrent paketi altında yer alır. Bir görev havuzu oluşturarak, zamanlanmış görevlerin sistem kaynaklarını tüketmeden, kontrollü bir şekilde çalışmasını sağlar. İlk adım olarak, bir executor servisi nasıl başlatılır görelim.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

public class ZamanlayiciBaslatma {
    public static void main(String[] args) {
        // 2 iş parçacıklı bir havuz oluşturuyoruz
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
        
        System.out.println("Zamanlayıcı başlatıldı.");
        
        // İşimiz bitince kapatmayı unutmamalıyız
        scheduler.shutdown();
    }
}

Yukarıdaki kodda newScheduledThreadPool(2) metodu ile iki iş parçacığına sahip bir havuz tanımladık. Bu havuz, aynı anda iki farklı zamanlanmış görevi işleyebilir. Uygulamanızın ihtiyaçlarına göre bu sayıyı artırabilirsiniz.

Tek Seferlik Zamanlanmış Görev Çalıştırma

Bazen bir görevin hemen değil, belirli bir süre geçtikten sonra bir kez çalışmasını istersiniz. Bunun için schedule metodunu kullanırız. Bu yöntem, özellikle e-posta gönderimi veya dosya temizliği gibi gecikmeli işlemler için idealdir.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class GecikmeliGorev {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

        scheduler.schedule(() -> {
            System.out.println("Görev 5 saniye sonra çalıştı!");
        }, 5, TimeUnit.SECONDS);

        scheduler.shutdown();
    }
}

Bu örnekte TimeUnit.SECONDS kullanarak 5 saniyelik bir gecikme belirledik. Lambda ifadesi içerisine yazdığımız kod, belirtilen süre dolduğunda arka planda çalıştırılır.

Düzenli Aralıklarla Görev Çalıştırma (Fixed Rate)

Uygulamalarınızda belirli bir periyotta (örneğin her 10 saniyede bir) çalışması gereken görevler için scheduleAtFixedRate metodu kullanılır. Bu yöntem, bir önceki görevin bitiş süresine bakmaksızın, sabit bir zaman aralığını takip eder.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class PeriyodikGorev {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        scheduler.scheduleAtFixedRate(() -> {
            System.out.println("Görev periyodik olarak çalışıyor: " + System.currentTimeMillis());
        }, 0, 5, TimeUnit.SECONDS);
    }
}

Burada dikkat edilmesi gereken nokta, eğer görev süresi 5 saniyeden uzun sürerse, bir sonraki görev bir önceki bitene kadar bekletilir. Bu, sistemin aşırı yüklenmesini önlemek için tasarlanmış bir güvenlik mekanizmasıdır.

Görevler Arası Gecikme ile Çalıştırma (Fixed Delay)

Eğer bir görevin bitişi ile diğerinin başlangıcı arasında sabit bir süre geçmesini istiyorsanız scheduleWithFixedDelay metodunu tercih etmelisiniz. Bu yöntem, özellikle yoğun işlem gerektiren görevlerde sistemin nefes almasını sağlar.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class GecikmeliPeriyodikGorev {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("Görev başladı.");
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Görev bitti. 3 saniye sonra tekrar başlayacak.");
        }, 0, 3, TimeUnit.SECONDS);
    }
}

Bu örnekte, görev bittikten sonra 3 saniyelik bir bekleme süresi eklenmiştir. Bu, görevlerin birbirini ezmesini engellemek için en sağlıklı yöntemdir.

Zamanlanmış Görevlerin Yönetimi ve Güvenlik

Zamanlanmış görevleri yönetirken, özellikle hata yönetimi ve kaynak temizliği konularında dikkatli olmalısınız. Bir görev içerisinde gerçekleşen kontrolsüz bir hata (exception), zamanlayıcının o görev için durmasına neden olabilir.

Kritik Uyarı: Üretim ortamında (production) zamanlanmış görevlerinizin içine mutlaka try-catch blokları ekleyin. Aksi takdirde, beklenmedik bir hata tüm zamanlayıcı iş parçacığını sonlandırabilir ve görevleriniz bir daha çalışmayabilir.

Aşağıdaki tablo, farklı zamanlama yöntemlerinin karşılaştırmasını sunmaktadır:

Metot Kullanım Amacı Avantajı
schedule() Tek seferlik gecikmeli işler Basit ve hatasız
scheduleAtFixedRate() Sabit aralıklı işler Zaman tutarlılığı yüksek
scheduleWithFixedDelay() İş bittikten sonra bekleme Kaynak tüketimi daha dengeli

Sıkça Sorulan Sorular

ScheduledExecutorService neden Timer sınıfından daha iyidir?

Timer sınıfı tek bir iş parçacığı kullanır ve bir görevdeki hata diğerlerini etkileyebilir. ScheduledExecutorService ise bir havuz kullanır, bu sayede görevler birbirinden bağımsız çalışabilir ve hata toleransı çok daha yüksektir.

Zamanlayıcıyı nasıl güvenli bir şekilde kapatırım?

Uygulamanız kapandığında scheduler.shutdown() metodunu çağırmalısınız. Eğer bekleyen görevlerin tamamlanmasını istiyorsanız shutdownNow() yerine shutdown() ve ardından awaitTermination() kullanmanız önerilir.

İş parçacığı havuzu boyutunu nasıl belirlemeliyim?

Bu, görevinizin yoğunluğuna bağlıdır. Eğer görevleriniz I/O ağırlıklı ise havuz boyutunu yüksek tutabilirsiniz, ancak CPU ağırlıklı işlemler yapıyorsanız işlemci çekirdek sayısına yakın bir değer seçmek performansı artırır.

Görevlerin çakışmasını nasıl önlerim?

scheduleWithFixedDelay metodunu kullanmak, bir görev bitmeden diğerinin başlamamasını garanti altına almanın en basit yoludur.

Zamanlanmış görevlerde veritabanı bağlantısı nasıl yönetilir?

Veritabanı bağlantılarını her görev içinde açıp kapatmak yerine bir bağlantı havuzu (Connection Pool) kullanmalısınız. Aksi takdirde bağlantı sızıntıları (leak) yaşanabilir.

Kod Güvenliği Sorumluluk Reddi: Bu makalede yer alan kod örnekleri eğitim amaçlıdır. Üretim ortamında kullanacağınız zamanlanmış görevlerde, hassas verilerin işlenmesi durumunda şifreleme yöntemlerini (AES-256 vb.) kullanmalı ve dışarıdan gelen verileri mutlaka valide etmelisiniz. SQL injection riskine karşı her zaman Prepared Statement tercih edin.

ScheduledExecutorService ile Hata Ayıklama ve İzleme Stratejileri

Zamanlanmış görevler arka planda sessizce çalıştığı için, bir hata oluştuğunda bu durumun fark edilmesi zor olabilir. ScheduledExecutorService içerisinde bir görev beklenmedik bir şekilde hata fırlatırsa (exception), görev durur ve hata yutulur. Bu durumun önüne geçmek için görevlerinizi mutlaka bir try-catch bloğu ile sarmalamalısınız.

Ayrıca, görevlerin çalışma sürelerini ve durumlarını izlemek için bir Decorator deseni veya Wrapper sınıfı kullanmak, sistemin sağlığını takip etmenizi kolaylaştırır.

public class MonitoringTask implements Runnable {
    private final Runnable task;

    public MonitoringTask(Runnable task) {
        this.task = task;
    }

    @Override
    public void run() {
        try {
            long startTime = System.currentTimeMillis();
            task.run();
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("Görev tamamlandı. Süre: " + duration + "ms");
        } catch (Exception e) {
            System.err.println("Görev sırasında kritik hata: " + e.getMessage());
            // Burada loglama sistemine veya izleme aracına bildirim gönderebilirsiniz
        }
    }
}

// Kullanımı:
executorService.scheduleAtFixedRate(new MonitoringTask(myTask), 0, 1, TimeUnit.MINUTES);

Zamanlanmış Görevlerde Birim Testi (Unit Testing)

Zamanlanmış görevlerin test edilmesi, kodun belirli bir süre geçmesini beklemesi gerektiği için zorlayıcı olabilir. Test süreçlerini hızlandırmak ve deterministik sonuçlar almak için ScheduledExecutorService nesnesini doğrudan kodun içine gömmek yerine, bir arayüz (interface) üzerinden enjekte etmelisiniz (Dependency Injection).

Test ortamında, gerçek zamanı beklemek yerine Awaitility gibi kütüphaneler kullanarak asenkron işlemlerin tamamlanmasını bekleyebilir veya ScheduledExecutorService yerine sahte (mock) bir yapı kullanarak zamanı kontrol edebilirsiniz.

Aşağıdaki örnek, bir görevin çalışıp çalışmadığını CountDownLatch kullanarak nasıl test edebileceğinizi gösterir:

@Test
public void testTaskExecution() throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(1);
    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    executor.schedule(() -> {
        // Test edilecek mantık
        latch.countDown();
    }, 100, TimeUnit.MILLISECONDS);

    boolean completed = latch.await(1, TimeUnit.SECONDS);
    assertTrue("Görev zamanında çalışmadı!", completed);
    executor.shutdown();
}

Performans ve Kaynak Yönetimi İpuçları

Uygulamanızda çok sayıda zamanlanmış görev varsa, tek bir ScheduledExecutorService örneğini paylaşmak kaynak tüketimini optimize eder. Ancak, uzun süren görevlerin kısa süreli görevleri engellememesi için dikkatli olunmalıdır.

  • Thread Havuzu Boyutu: Eğer görevleriniz I/O ağırlıklıysa (veritabanı, API çağrısı), havuz boyutunu işlemci çekirdek sayısından daha yüksek tutabilirsiniz. CPU ağırlıklı görevlerde ise çekirdek sayısı + 1 idealdir.
  • Kuyruk Dolması: scheduleAtFixedRate kullanırken, görevlerin çalışma süresi periyottan uzun sürerse kuyrukta birikme yaşanabilir. Bu durumu ThreadPoolExecutor üzerindeki RejectedExecutionHandler ile yöneterek sistemin aşırı yüklenmesini engelleyebilirsiniz.
Profesyonel İpucu: Eğer uygulamanızda yüzlerce farklı zamanlanmış görev yönetmeniz gerekiyorsa, ScheduledExecutorService yerine Quartz Scheduler gibi daha gelişmiş, kalıcılık (persistence) sağlayan ve gelişmiş tetikleyici mekanizmalarına sahip kütüphaneleri değerlendirmelisiniz.

Sonuç

Java ile ScheduledExecutorService kullanarak zamanlanmış görevler oluşturmak, uygulamanızın arka plan süreçlerini yönetmek için en profesyonel yaklaşımdır. Bu rehberde, basit bir gecikmeden karmaşık periyodik işlemlere kadar tüm temel yapıları adım adım inceledik. Bir sonraki adım olarak, Spring Framework içerisinde yer alan @Scheduled anotasyonunun bu altyapıyı nasıl daha da basitleştirdiğini araştırmanızı öneririm.

Bu yazıya tepkinizi paylaşın:
Selin Yılmaz

Sürdürülebilir yaşam ve pratik ev yönetimi üzerine içerik stratejileri geliştiriyorum. Yalın anlatımı ve uygulanabilirliği ön planda tutan bir yazı dilim var.

Yorumlar (0)

Yorum Yaz