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

}

Örnek program okumak için biraz uzun evet. Ancak yaptığı şey oldukça basit; iki thread ile “list1” ve “list2”ye 1000’er sayı eklemek. Listelere toplamda 2000’er sayı eklenmesi bekleniyor. Ayrıca “main” metodu içerisinde Application.work() metodu çağrılmadan önceki ve sonraki zamanı elde ederek yaklaşık çalışma süresini çıktı olarak yazdırıyor. Bu zamanın bize anlamlı görünmesi için daha önce gördüğümüz gibi Thread.sleep(1) çağrılarıyla ilgili threadi bekletiyor. Thread.start() ve Thread.join() çağrılarını ise zaten daha önce görmüş ve irdelemiştik. Gelelim programımızı çalıştırdığımızda -eğer şanslıysak- göreceğimiz çıktıya:

Geçen zaman: 2570
List 1'in boyutu: 1980
List 2'nin boyutu: 1980

Ancak her zaman göreceğimiz çıktı böyle olmayacaktır. Çoğu zaman aşağıdaki gibi bir çıktı görürüz:

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 15
    at java.util.ArrayList.add(ArrayList.java:459)
    at multithreading.chapter4.Application.addNewIntegerToList2(Application.java:74)
    at multithreading.chapter4.Application.process(Application.java:55)
    at multithreading.chapter4.Application.access$000(Application.java:7)
    at multithreading.chapter4.Application$1.run(Application.java:31)
    at java.lang.Thread.run(Thread.java:745)
Geçen zaman: 2556
List 1'in boyutu: 1009
List 2'nin boyutu 1009

“list1” ve “list2” değişkenleri -bir önceki bölümde gördüğümüz “count” değişkeni gibi- iki thread için ortak veridir (shared data). Yani iki thread aynı anda erişip üzerinde aynı anda işlem yapmaya kalkışabiliyor. Bu durum ise yukarıdakiler gibi tutarsızlıklara sebep oluyor. Bu gibi sıkıntıları çözmek için aklımıza gelen ilk yöntem -bir önceki bölümde öğrendiğimiz gibi- “addNewIntegerToList1” ve “addNewIntegerToList2” metotlarını “synchronized” yapmak olacaktır. Ve evet böyle yapmak önceki çıktılarda gördüğümüz gariplikleri ortadan kaldıracaktır:

...

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

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

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

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

Programımızı bu haliyle çalıştırdığımızda beklendiği gibi listelerin boyutunu her seferinde 2000 olarak görürüz:

Geçen zaman: 4960
List 1'in boyutu: 2000
List 2'nin boyutu: 2000

Ancak bu defa da çalışma süresi iki katına çıktı. Bunun nedeni daha önce sözünü ettiğimiz içsel kilittir (intrinsic lock). Örnek üzerinden açıklayacak olursak; evet “app” nesnesinin “synchronized” yaptığımız “addNewIntegerToList1” metodu bir thread tarafından çağrıldığında başka bir threadin “addNewIntegerToList1” metodunu çağırması için ilk çağıran threadin yaptığı çağrının sonlanmasını beklemesi gerekir. Ve aynı şekilde “app” nesnesinin “synchronized” yaptığımız “addNewIntegerToList2” metodu bir thread tarafından çağrıldığında başka bir threadin “addNewIntegerToList2” metodunu çağırması için ilk çağıran threadin yaptığı çağrının sonlanmasını beklemesi gerekir. Buraya kadar gayet normal. Fakat burada beklenmedik bir şey söz konusudur. Metot tanımında belirtilen “synchronized” kelimesi o metoda yapılan çağrıları bekletmek için içsel kilidi kullanır. Ve içsel kilit bir nesne için yalnızca bir tanedir. Bu şuna sebep olur: Bir thread “addNewIntegerToList1” çağrısı yaptığında “addNewIntegerToList1” metoduna yapılacak çağrıları bekletirken -içsel kilidin bir nesne için sadece bir tane olması sebebiyle- “addNewIntegerToList2” metoduna yapılacak çağrıları da bekletmiş olur. Fakat “list1” ve “list2” birbirinden ayrı ortak veriler oldukları için, birine erişildiğinde diğerine erişimin kısıtlanması gereksizdir. Bu sefer imdadımıza yetişen çoklu kilitler (multiple locks) ve “synchronized” kod bloklarıdır. “synchronized” kod blokları farklı kod parçaları için farklı kilitler belirtebilmemize olanak sağlar. İlk iş iki adet kilit nesnesi oluştururuz, ve “list1” ve “list2” listelerine yeni eleman eklediğimiz yerleri bu birbirinden ayrı kilitleri kullanacak şekilde birer “synchronized” kod bloğu olarak ifade ederiz:

public class Application {

    private Object lock1 = new Object();

    private Object lock2 = new Object();

    ...

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

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

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

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

}

Programımızı bu haliyle çalıştırdığımızda çalışma süresinin yaklaşık yarı yarıya düştüğünü görebiliriz:

Geçen zaman: 2787
List 1'in boyutu: 2000
List 2'nin boyutu: 2000

İçsel kilitteki gibi tek kilit olduğu durumu bu yeni yapı ile gözlemlemek amacıyla iki kod bloğunda da örneğin “lock1” kilidini belirtelim:

...

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

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

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

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

Bu durumda, tıpkı içsel kilitte olduğu gibi, çalışma süresi tekrar yaklaşık iki katına çıkacaktır:

Geçen zaman: 5049
List 1'in boyutu: 2000
List 2'nin boyutu: 2000
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