Developersland

Teknoloji ve Yazılım Blogu

  • Yazıtipi boyutunu arttır
  • Varsayılan yazıtipi boyutu
  • Yazıtipi boyutunu azaltır

Javada Oyun Programlaması. Part 1 (Threadler-2)

Threadlerde kullanılması gerekecek bazı önemli özellikler

Örneğin, oyunumuzu kapatmadan önce bütün oyuncuların oyundan çıkmasından emin olmamız lazım, bu durumda  join()  methodunu kullanabiliriz. 

myThread.join();

Bu durumda çalıştırmış olduğumuz thread’e “bu thread işlemi bittikten sonra dön” demiş oluyoruz.

Bazı durumlarda thread’imizin bir süre “uyumasını” isteyebiliriz.

Thread.sleep(1000);

Bu kod sistemde şuan çalışmakta olan thread’in 1 saniye (1000 millisaniye) uyumasına neden olacaktır.

Senkranizasyon

Çoklu Thread ortamında bazı sıkıntılar da çıkabilir. Örneğin, bir kontrolun olacağı yerde o kontrolun şartının başka bir thread tarafından değiştirilmesi gibi şeyler. Ve sonuçta bu tür sorunlar ‘hata nerede?’ diye bizi saatlerce uğraştırmış olur ve bazı durumlarda hatta gözden bile kaçar. Örnek olarak bir kitaptan aldığım şu kod parçasını sizinle paylaşmak isterim. Önce tabii kodun ne yaptığını kısaca anlatayım. Bir labirent oyunu yapacağız ve oyunun bitme şartı da olarak çıkış noktasının kordinatlarını da x=0, y=0 olarak alalım - eğer oyuncu x=0, y=0 kordinatına gelebilmişse demek labirentten çıkmıştır. Oyunumuzda aynı anda çalışan birkaç thread vardır ve onların herhangi biri x veya y değerlerine erişebiliyor:

public class Maze {


    private int playerX;

    private int playerY;


    public boolean isAtExit() {

        return (playerX == 0 && playerY == 0);

    }


    public void setPosition(int x, int y) {

        playerX = x;

        playerY = y;

    }

}


İlk baktığınızda kod doğru çalışır diyebilirsiniz ama sadece tek bir thread çalıştığı durumlarda. Çoklu thread durumu için oyuncunun (1,0) noktasından (0,1) noktasına geçtiğini düşünelim. Bu durumda kodumuz aşağıdaki gibi çalışacaktır: 


  1. Başlıyor, objenin playerX ve playerY değerleri uygun olarak 1 ve 0 olacaktır.
  2. A Thread’i setPosition(0,1) fonksiyonunu çağırıyor.
  3. playerX = x; satırı çalışıyor ve playerX = 0 oluyor.
  4. Thread B çalışmaya başlıyor ve Thread A’yı durduruyor.
  5. Thread B isAtExit()’i çağırıyor.
  6. Şuan için playerX ve playerY’nin ikisinin de değerleri 0‘dır. Demek isAtExit() metodumuzdan true dönecektir. 


Yukarıda anlatılmış durumda oyuncu labirenti çözemediği halde oyun yalnış bir sonuca varmış olacaktır ve oyunu sonlandıracaktır. Basit bir çözüm olarak setPosition() ve isAtExit() metodlarının aynı anda çalışmalarını engelleyebiliriz. Bunu yapmak için thread’leri senkronize etmemiz gerekir. Thread’leri senkronize etmek için sadece synchronized kullanmamız yetiyor. Bir önceki örneğimiz için thread-safe kodumuz aşağıdakı gibi olacaktır:


public class Maze {


    private int playerX;

    private int playerY;


    public synchronized boolean isAtExit() {

        return (playerX == 0 && playerY == 0);

    }


    public synchronized void setPosition(int x, int y) {

        playerX = x;

        playerY = y;

    }

}


Thread A setPosition() metodunu çalıştırdığı zaman synchronized keywordunu görecek ve JVM’den lock izni alacaktır. Bu durumda hiçbir başka synchronized dahilinde olan kod çalıştırılamaz, yani JVM’in tek bir lock’u var gibi düşünebiliriz; ilk olarak hangi thread o locku alırsa o kod çalışmasını bitirene kadar lock onda duracaktır bu yüzden lock talep eden kod kısımları (synchronized olanlar) aynı anda çalıştırılamayaktır. Bir kod parçası için synchronized(this) eklememiz gerekir:


public void myMethod() {


    synchronized(this) {

        // locku alacak kod parçası; thread-safe değildir

    }


    // thread-safe kod parçası

}


Tabii ki fazla synchronized kullanımı da iyi değildir. Thread-safe bir koda synchronized olarak çalış dersek bu sadece o uygulamanın hızını düşürmüş olur. Yani nerede synchronized kullanacağımızı da bilmemiz lazım ve komple bir metoda synchronized ekleyeceğimize sadece kodun belli kısımlarına eklememiz daha verimli olur. Sadece local değişken kullanan metodlara synchronized eklememiz mantık hatası olmuş olur. Çünkü local değişkenler stack’ta tutuluyor ve her thread’in de kendi stack’i olduğu için synchronization sorununun çıkması mümkün değildir.


public int square(int n) {

    int s = n * n;

    return s;

}


Nerenin thread-safe olduğundan şüpheniz varsa orayı alıp da garanti olsun diye synchronized yapmayınız, onun yerine sadece küçük bir not düşebilirsiniz ve sonra test ederken doğru çalışıp çalışmadığını denemiş olursunuz.




wait() ve notify() kullanımları


Farzedelim ki Thread A’nın Thread B’den gelen bir sonuca ihtiyacı vardır ve Thread B’nin o sonucu döndürmesini bekliyor. Bu durum için wait() ve notify() metodlarından faydalanabiliriz. 

wait() metodu synchronized kod parçası içerisinde çalıştırılıyor, ve wait() çalıştığı anda o lock JVM’e geri veriliyor ve thread’imiz uyumaya başlıyor ve aynı zamanda notify() olmasını bekliyor.

notify() metodu da synchronized kod parçası içerisinde çalıştırılıyor ve çalıştırıldığı zaman aynı sistemde notify() bekleyen ilk thread’i (FİFO) uyandırıyor. 


Anlattığım bu senaryonu örnek bir kod parçası üzerinde anlatayım:


Aşağıdaki örneğimizde 3 thread’in olduğunu farzedelim, bunların 3ü de MyThing objesinin lock’unu almaya gayret edecektir (yani 3ü de synchronized dir): thread isimleri waiter, notifier ve related olsun.


class MyThing { 

 

    synchronized void waiterMethod() { 

        // Bir işlem yap

 

        // Notifier sonucunu bekle

        wait(); 

 

        // Kaldığımız yerden devam ediyoruz

    } 

 

    synchronized void notifierMethod() { 

        // Bir işlem yap

 

        // Waiter’i uyandır

        notify(); 

 

        // Başka işlemler de yapabiliriz burada

    } 

 

    synchronized void relatedMethod() { 

        // burada da başka bir işlem yapabiliriz

    } 


JVM ilk olarak waiter threadini aldığını farzedelim, waiter de waiterMethod() ‘u çalıştıracaktır diyelim, o zaman lock şuan waiter’e geçecektir. waiter wait() metodunu çalıştırıyor ve uyumaya başlıyor, uyumadan önce waiter threadi lock’u MyThing class’ına geri veriyor artık lock başka threadler tarafından da kullanılabilir hale gelmiş oluyor. Sistemde lock’u almak isteyen notifier ve related thread’leri var şimdi, JVM bir sonrakı threadini kendi JVM durumuna ve thread önceliğine göre değerlendirecektir. JVM notifier’i seçti diyelim ve lock’u notifier’a verdi, lock artık notifier’da ve related threadi hala beklemekte, notifier notify() metodunu çalıştırınca waiter’i uyandırmış oluyor ve waiter de artık lock’u talep ediyor kaldığı yerden koduna devam etmesi için. Ama  lock hala notifier’de ve notifier notify() metodundan sonrakı kodlarını da çalıştırıp metoddan çıktıktan sonra lock’u JVM’e geri veriyor. Bir sonrakı aşamada kimin lock’u alacağı yine JVM tarafından belirlenecektir. 

Ve böylece thread’ler kendi aralarında haberleşmiş oluyorlar. Eğer birkaç thread wait() yaparak uyumuşlarsa o zaman notify() kullanımı sadece ilk wait() yapan threadi uyandıracaktır. Eğer hepsini uyandırmak istiyorsak notifyAll() metodunu kullanabiliriz.


Eğitimin Devamı...


 

Yorum ekle


Güvenlik kodu
Yenile