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

}

“main” metodumuz ise bir önceki bölümdekine benzer şeyi gerçekleştirecek; biri “produce()” diğeri “consume()” metodunu işletecek iki thread oluşturup başlatmak:

public class Application {

    public static void main(String[] args) throws InterruptedException {
        final Processor processor = new Processor();

        Thread producerThread = new Thread(() -> {
            try {
                processor.produce();
            } catch (InterruptedException e) {
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                processor.consume();
            } catch (InterruptedException e) {
            }
        });

        producerThread.start();
        consumerThread.start();

        producerThread.join();
        consumerThread.join();
    }

}

Örnek programımızın tamamı karşımızda olduğuna göre Processor sınıfımızı irdelemeye başlayalım: “produce()” metodu geçerli nesneyi kilit nesnesi (lock object) olarak kullanarak bir “synchronized” blok oluşturuyor. (Bir anlamda içsel kilit mekanizmasını kullanıyor.) Blok içerisinde, çalışmaya başladığını bildiren bir çıktı yazdırdıktan sonra o nesneye ait “wait()” metodunu çağırıyor. Bu çağrı ile geçerli threadin çalışması -sonradan devam etmek üzere- beklemeye alınıyor ve blok için belirtilen kilit serbest kalıyor. Kilit serbest kaldığı için başka threadler aynı kilide sahip bloklara girebilecekler.

İlk önce “produce()” metodundaki “synchronized” bloğa girildiğinden emin olmak için “consume()” metodunun başında “Thread.sleep(2000)” şeklinde bir çağrı yaptık. (Bunu yalnızca örneği anlaşılır kılmak için yaptık elbette.) Bu geçen sürede “produce()” metodu içerisindeki blokta “wait()” çağrısı yapıldı ve ilgili thread beklemeye alınarak blok için belirtilen kilit serbest bırakıldı. Kilidin serbest bırakılmasıyla, “produce()” metodu içerisindeki blok sonlanmamasına rağmen “consume()” metodu içerisindeki aynı kilit nesnesine sahip bloğa girilebildi. Bu bloğa girildiğinde önce bloğa girildiğine dair bir çıktı yazdırılıyor. Ardından kullanıcıdan ‘Enter’a basması beklenir. Kullanıcı ‘Enter’a bastıktan sonra en nihayetinde, bu bloğa ait kilide sahip ve beklemekte olan diğer threadi -artık devam edebileceğine dair- bilgilendirmek amacıyla, “notify()” çağrısı yapılır. Programı çalıştırdığımız ve bizden istendiğinde ‘Enter’a bastığımız taktirde çıktı şu şekilde olacaktır:

Üretici thread çalışıyor...
Tüketici thread çalışıyor...
Devam etmek için 'Enter'a basınız: 
Tüketici devam ediyor...
Tüketici 5 saniye daha devam etti.
Üretici devam ediyor...

DİKKAT! Çıktıda önemli bir ayrıntı var: “notify()” metodu çağrılır çağrılmaz “wait()” ile beklemeye alınan thread devam etmiyor. Çünkü “consume()” metodu içerisindeki blok henüz tamamlanmadı ve dolayısıyla ilgili kilit serbest bırakılmadı. Çıktıdan da anlaşılacağı gibi 5 saniye geçtikten sonra kilit serbest kalıyor ve üretici çalışmasına devam edebiliyor.

DİKKAT! “wait()” metodu yalnızca “synchronized” kod blokları (yada “synchronized” metotlar) içerisinden çağrılabilir. Ve “wait()” metodu çağrılan nesne yalnızca söz konusu “synchronized” blokla ilişkili kilit nesnesi olabilir. Diyelim ki “synchronized” blok için belirtilen kilit nesnesi “lockObject” adında bir nesne olsun. Bu durumda bu blok içerisinde çağrılacak “wait()” metodu yalnızca “lockObject“e ait olan metot olabilir:

Object lockObject = new Object();
...
synchronized (lockObject) {
    ...
    lockObject.wait();
    ...
}

Örneğimizde kilit nesnesini geçerli nesne yani “this” olarak belirttiğimiz için blok içerisinde geçerli nesneye ait “wait()” metodunu çağırdık. Aksi halde “IllegalMonitorStateException” adlı istisna fırlatılırdı.

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