Java’da Multithreading – Bölüm 13: Threadlerin Yarıda Kesilmesi (Interrupting)

Bu zamana kadar ki örneklerimizde “InterruptedException” istisnası ile sıkça karşılaştık. Bu istisna bir threadin çalışması yarıda kesildiğinde fırlatılır. Peki çalışmakta olan bir threadin çalışması nasıl yarıda kesilir? Gelin yaptığı işlem uzun süren bir threadi belli süre geçtikten sonra durdurmayı deneyelim. Bunun için “Thread” sınıfının sağladığı “interrupt()” metodunu kullanacağız:

public class Application {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Başladı.");

        Thread t = new Thread(() -> {
            for (int i = 0; i < 1E8; i++) {
                Math.sin(new Random().nextDouble());
            }
        });

        t.start();

        Thread.sleep(500);

        t.interrupt();

        t.join();

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

}

Programın çalışması bittiğinde çıktısının şu şekilde olduğunu göreceksiniz:Devamı »

Java’da Multithreading – Bölüm 12: “Callable” ve “Future”

Thread havuzlarını incelediğimiz bölümde görmüştük: “ExecutorService” sınıfının “submit” metoduna yapılacak işleri birer “Runnable” nesnesi şeklinde geçeriz, o da her bir “Runnable” nesnesi için bir thread oluşturur ve başlatır. “submit” metodu eklediğimiz her bir “Runnable” için bir “Future” döndürür. Bu “Future” nesneleri sayesinde asenkron işletilen bu görevlerin tamamlanıp tamamlanmadığını, yoksa iptal mi edildiklerini kontrol edebilir, hatta o görevleri iptal edebiliriz. Ayrıca yine bu “Future” nesnelerini kullanarak threadlerin dönüş değerlerini elde edebiliriz. Şaşırmış olmalısınız, çünkü “Runnable” arabiriminin “run()” metodu değer döndürmez, yani “void”dir. Değer döndürme ihtiyacımız olduğu durumlarda Java eş zamanlılık kütüphanesinde yer alan “Callable” adlı arabirimi kullanabilir, “submit” metoduna görevlerimizi “Callable” nesneleri olarak geçebiliriz. “Callable” arabirimi jenerik tiplidir ve sahip olduğu tek metot olan “call()” metodu “Callable” jeneriği için belirtilen tipte bir değer döner.

Aşağıda “submit” metoduna rastgele bir sayı dönen bir “Callable” geçtik. Çıktı olaraksa “Future” nesnesi üzerinden elde ettiğimiz bu rastgele sayıyı yazdıracak:

public class Application {

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

        Future future = executorService.submit(new Callable() {
            @Override
            public Integer call() throws Exception {
                Random random = new Random();
                return random.nextInt();
            }
        });

        executorService.shutdown();

        try {
            Integer result = future.get();
            System.out.println("Sonuç: " + result);
        } catch (ExecutionException e) {
            System.out.println("Çalışma zamanı hatası!");
        } catch (InterruptedException e) {
        }
    }

}

Devamı »

Java’da Multithreading – Bölüm 11: Semaforlar

Semaforlar (Semaphores) -genelde- bir kaynağa erişimi kontrol etmek için kullanılan sayaçlardır. Kilit mekanizmaları gibi threadlerin senkronizasyonu için kullanılırlar fakat kilitlerden farklı olarak bağımsız threadler tarafından serbest bırakılabilirler (release).

HATIRLATMA! Bir thread tarafından elde edilmiş kilidi başka bir thread serbest bırakmaya kalktığında ”IllegalMonitorStateException” istisnası fırlatılır.

Örneğin programımızın bir sunucuya eş zamanlı isteklerde bulunması, ancak programımızdan o sunucuya eş zamanlı maksimum üç bağlantı olmasını istiyoruz. Tam da semaforlarla çözülebilecek bir durum. Kısıtlı kaynağımız “bağlantı sayısı”, üst sınır değeri ise “3”. Öyleyse 3 izinli (permit) bir semafor işimizi görecektir.

“java.util.concurrent.Semaphore” sınıfı kurucusu “permit” (izin) adlı bir parametre almaktadır. Bu değer ilgili kısıtın üst sınır değerini ifade eder. Örneğimiz için ifade edecek olursak, her bir thread bağlantı oluşturdukça semaforun “acquire()” (elde et) metodu ile semafordan bir izin alacaktır. İki thread daha bağlantı oluşturduğunda izin değeri “0” olacağından yeni bir thread izin istediğinde verilecek izin kalmadığından yeni threadler bekletilecektir. Ta ki önceki threadler işlerini bitirip -semaforun ”release()” metodunu kullanarak- aldıkları izni iade edene kadar.

Örnek programımız sunucu bağlantısı kurmaya çalışan 10 adet threadi oluşturup başlatan bir “main” metodu ve kontrollü bir şekilde sunucu bağlantılarını gerçekleştiren bir “Connection” sınıfından meydana gelmekte:

public class Application {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                try {
                    Connection.getInstance().connect();
                } catch (InterruptedException e) {
                }
            });
        }

        executorService.shutdown();
    }

}

“Connection” sınıfı:Devamı »

Java’da Multithreading – Bölüm 10: Kilitlenme (Deadlock)

Bu bölümde çok threadli programların baş belası kilitlenme/tıkanma (deadlock) durumunu inceleyeceğiz.

Bu bölümdeki örnek programımızda iki banka hesabı arasındaki para transferini örnekleyeceğiz. Bir banka hesabını tanımlayan “Account” (Hesap) sınıfı aşağıdaki gibidir:

public class Account {

    private int balance = 10000;

    public static void transfer(Account sourceAccount, Account targetAccount, int amount) {
        sourceAccount.withdraw(amount);
        targetAccount.deposit(amount);
    }

    public void deposit(int amount) {
        balance += amount;
    }

    public void withdraw(int amount) {
        balance -= amount;
    }

    public int getBalance() {
        return balance;
    }

}

Devamı »

Java’da Multithreading – Bölüm 9: Yeniden Girilir Kilitler (Re-entrant Locks)

Bu bölümde “synchronized” anahtar kelimesinin işlevine alternatif bir yöntem olan yeniden girilir kilitleri (re-entrant locks) inceleyeceğiz.

Yeniden girilir kilitlerin çalışma mantığı şu şekildedir: java.util.concurrent.locks.* paketi altındaki “ReentrantLock” sınıfı türünde yeniden girilir bir kilit oluşturulur. Aynı anda yalnızca bir threadin girmesini istediğimiz kod bloğu yeniden girilir kilidin “lock()” ve “unlock()” metotlarıyla çevrelenir.

Şimdi örnek programımız üzerinden incelememize devam edelim:

public class Runner {

    private int count = 0;

    private Lock lock = new ReentrantLock();

    private void increment() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

    public void firstThread() {
        lock.lock();
        increment();
        lock.unlock();
    }

    public void secondThread() {
        lock.lock();
        increment();
        lock.unlock();
    }

    public void printCount() {
        System.out.println("Sayaç: " + count);
    }

}

Devamı »

Java’da Multithreading – Bölüm 8: Beklet ve Bildir (Wait and Notify)

Bir önceki bölümde Producer-Consumer yapısını ve bir kuyruk ile nasıl gerçekleştirebileceğimizi incelemiştik. Kuyruğa eleman eklerken veya kuyruktan eleman alırken bazı koşullara göre ilgili threadlerin bekletildiğinden söz etmiştik. Bu bölümde bu işlevselliği kendi çok threadli programlarımız için nasıl gerçekleştirebileceğimizi öğreneceğiz.

Java’da her sınıf otomatik olarak “Object” sınıfından türer. “Object” üst sınıfına ait “wait()” ve “notify()” metotları yukarıda bahsettiğimiz bekleme ve tekrar sürdürme işlevselliğini sağlayabilmek için kullanılırlar. Bu bölümde “wait()” ve “notify()” metotlarının ne işe yaradıklarına ve nasıl kullanıldıklarına basit bir örnekle giriş yapmış olacağız.

Üretici ve tüketici metotlara yani “produce()” ve “consume()” metotlarına sahip Processor sınıfımız aşağıdaki gibi olsun:

public class Processor {

    public void produce() throws InterruptedException {
        synchronized (this) {
            System.out.println("Üretici thread çalışıyor...");
            wait();
            System.out.println("Üretici devam ediyor...");
        }
    }

    public void consume() throws InterruptedException {
        Thread.sleep(2000);

        Scanner scanner = new Scanner(System.in);

        synchronized (this) {
            System.out.println("Tüketici thread çalışıyor...");
            System.out.print("Devam etmek için 'Enter'a basınız: ");
            scanner.nextLine();
            System.out.println("Tüketici devam ediyor...");
            notify();
            Thread.sleep(5000);
            System.out.println("Tüketici 5 saniye daha devam etti.");
        }
    }

}

Devamı »

Java’da Multithreading – Bölüm 7: Producer-Consumer Yapısı

Producer (Üretici) ve Consumer (Tüketici) yapısı hem günlük hayatta hem de programlama yaparken sıkça karşılaşabileceğimiz bir yapıdır. Producer kuyruğa bir şeyler ekler, Consumer ise bu kuyrukta bir şeyler oldukça sırayla alır ve ne yapması gerekiyorsa yapar. Günlük hayattan bir örnek verecek olursak; bir bankada müşteriler için sıra numarası üreten cihaza Producer, işlem görmemiş sıra numaralı müşteri oldukça onları LED sıra göstergeleri aracılığıyla çağırıp işlem yapan vezne görevlisi de Consumer olarak düşünülebilir.

Siz de fark etmişsinizdir, Producer ve Consumer birbirinden bağımsız iki ayrı thread halinde gerçekleştirilebilir. Ancak burada yine ortak bir veri kaynağımız olduğunu gözden kaçırmayalım: Kuyruk (Queue). Yine yardımımıza yetişen java.util.concurrent.* paketinden “thread-safe” bir yardımcı sınıf olan “BlockingQueue” oluyor. BlockingQueue aslında bir arabirim (interface) ve biz onun gerçekleştirimlerinden (implementation) biri olan “ArrayBlockingQueue” sınıfını kullanacağız. ArrayBlockingQueue tipinde bir kuyruk oluştururken bir kapasite belirtiriz. Bu kapasite dolu ise kuyruğa yeni eleman eklemek isteyen thread bekletilir. Benzer şekilde eğer kuyrukta eleman yoksa kuyruktan eleman almak isteyen thread kuyrukta eleman oluncaya kadar bekletilir.

Şimdi Producer-Consumer yapısını ve ArrayBlockingQueue sınıfının kullanımını örnekleyeceğimiz programımızı inceleyelim:Devamı »

Java’da Multithreading – Bölüm 6: Geri Sayım İçin CountDownLatch Yardımcı Sınıfı

Java’nın dahili kütüphanelerinde “multithreading” programlamaya dair pek çok yardımcı sınıf mevcuttur. (java.util.concurrent.*) Bunlardan biri de bu bölümde ele alacağımız “CountDownLatch” sınıfıdır. “Count down” “geri sayım”, “latch” “kapı sürgüsü/kilit mandalı” manalarına gelmektedir.

Geri sayım tamamlanıncaya kadar CountDownLatch örneğinin “await()” metodunu çağıran tüm threadler bekletilir. Geri sayım tamamlandığında bir anlamda kapı sürgüsü açılır ve bekleyen threadler işlemeye devam eder. Örneğin “n” sayıda threadin işlerini bitirdiklerini bildirene kadar (yani her biri sayacı bir azaltana ve nihayetinde sayacın değeri sıfıra ulaşana kadar) ana threadin bekletilmesi istediğimiz bir durumda kullanılabilir:

public class Application {

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(3);

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 3; i++) {
            executorService.submit(new Thread(new Processor(latch)));
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
        }

        System.out.println("Program bitti.");
    }

}

class Processor implements Runnable {

    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println("Thread başladı.");

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

        latch.countDown();
    }

}

Devamı »

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);
    }

}

Devamı »

Java’da Multithreading – Bölüm 4: “synchronized” Kod Blokları

Önceki bölümde bir metodu aynı anda birden fazla threadin işletememesini istediğimizde “synchronized” anahtar kelimesini nasıl kullanabileceğimizi örneklemiştik. Bu bölümde ise “synchronized” anahtar kelimesinin imdadımıza yetiştiği bir başka durum üzerinde duracağız. Yine bir örnek ile açılışı yaparak derman arayacağımız derdi ortaya koyalım. Özet olarak iki tam sayı (integer) listemiz var ve bu listeleri rastgele sayılarla doldurmak istiyoruz:

public class Application {

    private List<Integer> list1 = new ArrayList<>();

    private List<Integer> list2 = new ArrayList<>();

    public static void main(String[] args) {
        Application app = new Application();

        long startTime = System.currentTimeMillis();

        app.work();

        long endTime = System.currentTimeMillis();

        System.out.println("Geçen zaman: " + (endTime - startTime));
        System.out.println("List 1'in boyutu: " + app.list1.size());
        System.out.println("List 2'nin boyutu: " + app.list1.size());
    }

    private void work() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                process();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                process();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
        }

    }

    private void process() {
        for (int i = 0; i < 1000; i++) {
            addNewIntegerToList1();
            addNewIntegerToList2();
        }
    }

    private void addNewIntegerToList1() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }

        list1.add(new Random().nextInt());
    }

    private void addNewIntegerToList2() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }

        list2.add(new Random().nextInt());
    }

}

Devamı »