Java’da Multithreading – Bölüm 5: Thread Havuzları (Thread Pools)

Diyelim ki x tane görevimiz olsun ve mesela bu görevleri y’li threadler halinde yerine getirmek isteyelim. Java’da bunu nasıl gerçekleştirirdik? Java’nın bu ihtiyaca karşılığı thread havuzlarıdır, yani İngilizce tabiriyle Thread Pools.

Örneğin aşağıdaki gibi bir Processor sınıfımız olsun. “run()” metodu daha önce öğrendiğimiz üzere kendisini işletecek threadin yerine getireceği görevi ifade ediyor:

class Processor implements Runnable {

    private int id;

    public Processor(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("Başlıyor: " + id);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }

        System.out.println("Tamamlandı: " + id);
    }

}

Processor sınıfının “run()” metodu basitçe bize başladığını bildiriyor, işini yapıyor (görüldüğü üzere işi yalnızca 5 saniye beklemek), ve nihayet bittiğini bildiriyor.

Diyelim ki Processor sınıfının ifade ettiği görevi 5 kere yapmamız gerekti. Ve donanımsal olarak 2 işlemci çekirdeğimiz mevcut, yani aynı anda 2 thread işletebiliriz. Elle 2 thread oluşturup ve bu threadlerin sonlanmasını bekleyip 2 thread daha oluşturarak devam edebiliriz. Ancak böyle yapmak yerine Java’nın bize sağladığı yardımcı sınıflar (util classes) yardımıyla 2 thread büyüklüğünde bir thread havuzu oluşturup yapmak istediğimizi çok daha kolay ve olması gerektiği şekilde yapabiliriz.

2 boyutlu bir thread havuzu oluşturmak için java.util.concurrent.Executors sınıfının “newFixedThreadPool()” adlı statik metodundan yararlanacağız:

public class Application {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        for (int i = 1; i <= 5; i++) {
            executorService.submit(new Processor(i));
        }

        executorService.shutdown();

        System.out.println("Tüm görevler eklendi.");

        try {
            executorService.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
        }

        System.out.println("Tüm görevler tamamlandı.");
    }

}

“Executors.newFixedThreadPool(2)” çağrısı bize 2 threadlik bir havuz oluşturup görevlerimizi kabul edip işletecek olan bir java.util.concurrent.ExecutorService nesnesi döner. “ExecutorService.submit()” metodu ile Processor tipindeki görevlerimizi görev işleticiye (executorService) ekleriz. “ExecutorService” havuzunda yer olduğu sürece kendisine eklenen Runnable nesneleri ile yeni birer thread oluşturur ve onu başlatır. Örneğimizdeki gibi bir “ExecutorService” şöyle çalışmaktadır: Kendisine eklenen ilk görevi hemen işletmeye başlar. İkinci görev eklenir eklenmez hemen onu da işletmeye başlar. Ancak üçüncü görev geldiğinde önce havuzunda yer açılmasını bekler, yani ilk iki görevden birinin bitmesini. Bir diğer deyişle havuzdaki görevlerden biri tamamlanana kadar diğer görevler kuyrukta bekler. Ve tüm görevler bitene kadar bu şekilde devam eder.

“ExecutorService.shutdown()” çağrısı önemlidir. Şunu ifade eder: Görev işleticiye (executorService) yeni görev kabul etme ve mevcuttaki tüm görevler bittiğinde görev işleticiyi sonlandır. Eğer bu çağrı yapılmazsa, yukarıdaki program sonlanmayacak, yeni görevler beklemeye devam edecektir. Biz de örneğimizde “shutdown()” çağrısı sonrası bunu belirtmek amacıyla konsola “Tüm görevler eklendi.” şeklinde bir bilgilendirme mesajı yazdırıyoruz. “shutdown()” çağrısı sonrasında “executorService.submit()” çağrısı ile yeni görev eklemeye kalkarsanız “java.util.concurrent.RejectedExecutionException” tipinde bir istisna (exception) fırlatıldığını görebilirsiniz.

Peki tüm görevlerin tamamlanmasının ardından bir şeyler yapmak istersek? Bunun için “ExecutorService.awaitTermination()” metodu kullanılır. “awaitTermination()” metodu ile bir zaman sınırlaması belirtilir. Metodun çağrısında belirtilen zaman her koşulda beklenecek zamanı değil maksimum beklenecek zamanı belirtir. Örneğimizde bu zamanı 1 gün olarak belirttik. Bu şu demek oluyor: Tüm görevler tamamlanana kadar bekle. Eğer 1 günde tüm görevler tamamlanmazsa görev işleticiyi sonlandır.

Şimdi programı çalıştırıp çıktıyı görme zamanı:

Başlıyor: 1
Başlıyor: 2
Tüm görevler eklendi.
Tamamlandı: 2
Tamamlandı: 1
Başlıyor: 3
Başlıyor: 4
Tamamlandı: 3
Tamamlandı: 4
Başlıyor: 5
Tamamlandı: 5
Tüm görevler tamamlandı.

“run()” metodunda 5’er saniye beklettiğimiz için thread havuzlarının çalışma mantığını görsel olarak da gözlemleyebiliriz.

1. ve 2. görevin, 3. ve 4. görevin tamamlanma sırası kendi içlerinde değişebileceği için çıktı da değişebilir. Ancak threadlerin çalışmaya başlama sıraları her zaman aynı olacaktır.

Reklamlar

Bir Yorum Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Connecting to %s