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

}

“Runner” sınıfımızın eş zamanlı iki ayrı thread içerisinde işletilecek iki metodu var: “firstThread()” ve “secondThread()”. “main” metodumuz ise önceki bölümlerle benzer şeyi yapacak; bahsi geçen eş zamanlı iki threadi oluşturup başlatacak ve threadler sonlandığında “Runner” sınıfının “printCount()” metodu ile sayacın son durumunu yazdıracak. Bu nedenle “main” metodunu burada vermiyorum.

Bir thread “lock()” metodunu çağırarak o kilidi elde ettiğinde “lock()” çağrısı ile o kilidi elde etmek isteyen diğer threadler bekletilir. Kilidi elde etmiş thread içerisinde yeniden girilir kilidin “unlock()” metodu çağrılana kadar da bekletilmeye devam edilir. Böylece “increment()” metodu çağrıları sanki “synchronized” blok içerisindeymişcesine bir işlevsellik sağlanmış olur. Programı çalıştırdığımızda çıktı her seferinde şu şekilde olacaktır:

Sayaç: 20000

Diğer threadlerin kilidi elde edebilmesi için kilidi elde etmiş threadin kilidi serbest bırakması gerekir. Kilidi “ReentrantLock” nesnesine erişimi olan başka bir threadin serbest bırakması söz konusu değildir. Böyle bir durumda “IllegalMonitorStateException” istisnasının fırlatıldığını göreceksiniz. Dolayısıyla kilidi elde etmiş threadin bunu kesin olarak gerçekleştirmesi gerekmektedir. Peki ya çalışması sırasında bir istisna fırlatılır ve o thread henüz “unlock()” çağrısını yapamadan sonlanırsa? İşte bu nedenle “lock()” ve “unlock()” çağrıları arasında kalan kod bloğunun “try”, “unlock()” çağrısının “finally” içerisine alınması yerinde bir pratik olacaktır. Böylece istisna fırlatılması halinde dahi kilit serbest bırakılmış olur:

public class Runner {

    ...

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

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

    ...

}

Yeniden girilir kilitlerin “synhronized” kod bloklarına bir alternatif olarak nasıl kullanılabileceklerini öğrenmiş olduk. Peki önceki bölümde incelediğimiz ve bize koşula göre bir threadi bekletme ve sonrasında sürdürme işlevselliğini kazandıran “wait()” ve “notify()” metotlarının bu alternatif yöntemdeki karşılığı nedir?

Bunun için “lock.newCondition()” çağrısından elde edilecek bir “java.util.concurrent.locks.Condition” nesnesine ihtiyacımız olacak. “Condition” sınıfının “await()” metodu “wait()” metoduna, “signal()” metodu ise “notify()” metoduna karşılık gelmektedir.

Şimdi “Condition” nesnesini ve bu metotları kullanarak “Runner” sınıfımızı önceki bölümde yer alan “Processor” sınıfına benzer bir yapıya kavuşturalım:

public class Runner {

    private int count = 0;

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

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

    public void firstThread() throws InterruptedException {
        lock.lock();

        System.out.println("Thread 1 çalışıyor...");

        condition.await();

        System.out.println("Thread 1 devam ediyor...");

        try {
            increment();
        } finally {
            lock.unlock();
        }
    }

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

        lock.lock();

        System.out.println("Thread 2 çalışıyor...");
        System.out.print("Devam etmek için 'Enter'a basınız: ");

        new Scanner(System.in).nextLine();

        condition.signal();

        System.out.println("Thread 2 devam ediyor...");

        try {
            increment();
        } finally {
            lock.unlock();
        }
    }

    ...

}

Önce birinci threadin kilidi elde ettiğinden emin olmak için ikinci threadi 2 saniyeliğine bekletiyoruz. Programı çalıştırdığımızda çıktımız aşağıdaki şekilde olacaktır:

Thread 1 çalışıyor...
Thread 2 çalışıyor...
Devam etmek için 'Enter'a basınız: 
Thread 2 devam ediyor...
Thread 1 devam ediyor...
Sayaç: 20000
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. Çıkış  Yap / Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Connecting to %s