25 Eylül 2017 Pazartesi

Java Threadler

Merhaba arkadaşlar,

Bu yazımda sizlere Java'da kullanılan Thread mantığını anlatacağım.



Thread programlama CPU kullanım oranını ( utilization ) arttırmak, CPU'u boş durumda bırakmamak amacıyla geliştirilmiştir.



Java dilinin en büyük özelliklerinden biride multithread uygulamalar geliştirmemize olanak sağlamasıdır. Günümüz işlemci teknolojilerinin gelişmesi ile beraber  işlemcileri daha efektif kullanabilmek adına ön plana çıkan başlıca programlama yöntemlerinden biri oldu.


Multithread uygulamalar günümüzde bir çok kritik sistemlerin altyapısını oluşturan senkronizasyon ve sıralı işlem yapabilmenin ön plana çıktığı uygulamalarda sıkça kullanılmaktadır.


Multithread uygulama geliştirerek aynı anda birden fazla iş parçacığının çalışacağı uygulamalar yazmak mümkün olacaktır. Bu daha öncede bahsettiğim gibi işlemcileri daha performanslı kullanabilme adına büyük bir avantajmış gibi gözüksede, concurrent ( eş zamanlı ) çalışmanın vermiş olduğu sıkıntılarıda ön plana çıkarmaktadır.



Thread ( iş parçacığı ) kullanımı, birden fazla işlemin tek bir akışı paylaşarak neredeyse eş zamanlı bir şekilde gerçekleşmesini sağlar.


Threadler kullanılarak eş zamanlı istediğimiz kadar işlem yaptırabiliriz ancak unutulmaması gereken, oluşturulan her thread'in sistemin belleğinden ve işlemciden bir pay aldığıdır. Aşırı sayıda Thread oluşturulması mikroişlemcinin işlemler arası geçiş yapması gerektiğinden ciddi performans kayıplarına yol açacaktır.


Fiziksel olarak tek işlemcili bilgisayarlarda runnable durumunda ne kadar thread olursa olsun her hangi bir anda yalnızca tek bir thread running durumunda olabilir. Tek işlemcinin parallel işlem kapasitesi teorik olarak 1 dir.


Threadlerin start edilme sırasına göre çalıştırılmasını java garanti etmez. Önce start edilen threadin once çalışması java tarafından granti edilmez. Bu yüzden threadlerin senkronizasyonuna dikkat edilmelidir.


Running durumunda olan bir thread wait(), sleep(),  join() metodları çağırılarak blocked veya waiting durumuna geçebilir.


Bir thread blocked veya waiting durumuna, synchronized bir kod bloğuna girebilmek için gerekli lock’ı alamadığından dolayıda geçebilir. Synchronized tanımlanmış kod bloğu OS kavramlarından da bildiğimiz critical section kavramına denk gelir. Syncronized tanımlanmış kod bloğuna aynı anda yalnızca tek bir thread girebilir. Bu thread bu bölümünde işini bitirene kadar o bloğa girmek isteyen threadler sırada beklerler.


Thread oluşturmanın iki yolu mevcuttur.

Runnable interfacesini implement ederek
Thread classını extend ederek
oluşturulabilir. 

Ben yazımda Runnable interfacesini implement ederek Thread kullanımı açıklayacağım. Runnable interfacesini implement ederek kullanmak daha tercih edilebilir bir durumdur. Çünkü Thread class'ı extend edilirse başkka bir class'ı extend edemeyiz ve ihtiyaç halinde bir problem oluşur ama Runnable interfacesini implement edersem başka bir interface'yide extra implement edebilirim. Javanın bu kolaylığını kullanabilmek için Runnable interfacesini kullanacağım.



Runnable Interfacesi İle


Runnable interfacesi implement edilmelidir ve run () metodu override edilmek zorundadır.  Aşağıda örnekler  adım adım açıklanmıştır.



İlk olarak bir Maven projesi açılır.


MyThread class ı oluşturulur.




--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;

public class MyThread implements Runnable{



    private int    end;
    private String name;

    public MyThread(String name, int end) {

        this.end = end;
        this.name = name;
    }

    public void run() {

        for (int i = 0; i < end; i++) {
            System.out.println(name + " : " + i);
        }
    }

}


--------------------------------------------------------------------------------------------------------------
package ThreadOrnegi.com.serdar;

public class App 
{
    public static void main( String[] args )
    {
       
    Thread thread1 = new Thread(new MyThread("thread1", 6));
    Thread thread2 = new Thread(new MyThread("thread2", 6));
     thread1.start();
    thread2.start();
}
--------------------------------------------------------------------------------------------------------------
Çalıştırıldığında console ekranında 

thread1 : 0

thread1 : 1
thread1 : 2
thread1 : 3
thread2 : 0
thread2 : 1
thread2 : 2
thread2 : 3
thread2 : 4
thread1 : 4
thread1 : 5

thread2 : 5



çıktısını oluştu. Çıktıyı incelediğimizde iki thread te işlem yapıyor ve hangisinin önce hangisinin daha sonra biteceği garanti edilmiyor.





Öncelik Belirleme ( setPriority ) 



Yukarıda verilen MyThread class'ımızı temel alarak konu anlatımına devam edeceğim.


Threadlerin çalışma önceliklerini belirlemek için setPriority metodu kullanılır. Aşağıdaki örneğe bakıldığında thread1 öncelikli olarak start edilmesine rağmen thread2  önceliklendirildiği için işlemini ilk bitirir.




--------------------------------------------------------------------------------------------------------------
package ThreadOrnegi.com.serdar;

public class App 
{
    public static void main( String[] args )
    {
       
     Thread thread1 = new Thread(new MyThread("thread1", 6));
     Thread thread2 = new Thread(new MyThread("thread2", 6));


     thread1.setPriority(Thread.MIN_PRIORITY);

    thread2.setPriority(Thread.MAX_PRIORITY);
     thread1.start();
     thread2.start();
}
--------------------------------------------------------------------------------------------------------------



thread1 : 0

thread2 : 0
thread2 : 1
thread2 : 2
thread2 : 3
thread2 : 4
thread2 : 5
thread1 : 1
thread1 : 2
thread1 : 3
thread1 : 4
thread1 : 5





Executor  ile Threadlerin Kontrolü ve Düzenlenmesi 



Thread sayısının ve çalışmasının düzeni ve kontrollü bir şekilde gerçekleştirilmesi için Java bize Executor adında bir sınıf sunar.


Bu sınıf ile belli bir anda en fazla kaç Thread çalışabileceğini belirleriz. Aşağıdaki örneği inceleyelim:



--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class App 

{
    public static void main( String[] args )
    {
       


    ExecutorService executor = Executors.newFixedThreadPool(5);

   
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(new MyThread("thread" + i, 3));
            executor.execute(thread);
        }
        
        executor.shutdown();
        
        try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
        System.out.println("Done");
        
    }
}

--------------------------------------------------------------------------------------------------------------


Yukarıdaki kodu incelersek  newFixedThreadPool() metodu belli bir anda en fazla kaç Thread çalıştırmamız gerektiğini sorar.  Bu metod ile 5 farklı Thread aynı anda çalışabileceği belirtilmiştir.

for döngüsünde 20 adet Thread yaratılmasına rağmen executor servisi gelen işleri düzene sokar ve 5'ten fazla Thread üzerinde işlem gerçekleştirmez. Sonradan eklenen işler sıraya sokulur ve mevcut olanlar bittikçe çalıştırılır.

Bu sayede kaynaklar Threadler tarafından kontrolsüzce harcanmaz.

shutdown metodu ise yeni işlem alımını durdurur ve mevcut işlerin bitirilmesini sağlar.
awaitTermination ise mevcut işlemlerin bitirilmesi için belirli bir süre sanıt ve bu süre sonunda ExecutorService tamamen kapatılır.





ScheduledExecutorService  ile Threadlerin Kontrolü ve Düzenlenmesi




Java bizlere ScheduledExecutorService ile tanımlanan işlemlerin belirli bir zaman sonra otomatik olarak başlatılması ya da sistematik olarak belirli frekanslar ile çalıştırılmasını sağlar. Bu sistem ile yapılan işlemlerin belirli tarihlerde başlamasını veya sürekli tekrar edilmesini sağlayabiliriz.





--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class App 

{
    public static void main( String[] args )
    {
       
    Thread thread1 = new Thread(new MyThread("thread1", 6));
    Thread thread2 = new Thread(new MyThread("thread2", 6));

   

    ScheduledExecutorService pool1 = Executors.newScheduledThreadPool(5);
     pool1.schedule(thread1, 5, TimeUnit.SECONDS);
    pool1.schedule(thread2, 10, TimeUnit.SECONDS);
    try {
    System.out.println("start");
Thread.sleep(20000);
System.out.println("stop");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
    pool1.shutdown();


    }
}

--------------------------------------------------------------------------------------------------------------



Yukarıdaki örneğe bakıldığında  ExecutorService yerine ScheduledExecutorService kullanılmıştır. ScheduledExecutorService kullanılarak işlemlerin ileriki bir zamanda gerçekleştirilmesi sağlanmıştır.


Kodu incelersek;

thread1 ve thread2 iş parçacıkları 5 ve 10 saniye sonra başlaması için ayarlanmıştır.
main()'i koşturan ana Thread ise "start" yazdıktan sonra Thread.sleep(20000) 20 saniye uyutulduktan sonra çalıştırılmıştır. Yani "stop" yazmaktadır.



Thread'lerde Senkronizasyon ve Eş zamanlı(Concurrent) Erişim Problemleri



Birden fazla thread'in tanımlanan kritik kod bloğuna aynı anda girmesini engelleyen synchronized yapısı mevcuttur.


Synchronized  olarak methodlar ve kod blokları tanıtılabilir.


Örnek olarak aşağıdaki kodlar incelenebilir.


Synchronized Method Örneği:
--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;

public class SyncOutput {


public static synchronized void Goster(String threadname, int cnt)
{
System.out.println(threadname + " " + cnt);

}


}

--------------------------------------------------------------------------------------------------------------


package ThreadOrnegi.com.serdar;


public class MyThread implements Runnable{



    private int    end;
    private String name;

    public MyThread(String name, int end) {

        this.end = end;
        this.name = name;
    }

    public void run() {

        for (int i = 0; i < end; i++) {
               SyncOutput.Goster(name, i);
        }
   
    }


}

--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class App 

{
    public static void main( String[] args )
    {
       
    Thread thread1 = new Thread(new MyThread("thread1", 6));
    Thread thread2 = new Thread(new MyThread("thread2", 6));
    Thread thread3 = new Thread(new MyThread("thread3", 6));

    thread1.start();

    thread2.start();
    thread3.start();
   
    }

}



--------------------------------------------------------------------------------------------------------------



Yukarıdaki kodlar incelendiğinde SyncOutput class'ının Goster methodu synchronized olarak tanıtılmıştır. MyThread içerisinde run() methodunda bu synchronized method çağrılıyor.


Yani MyThread olarak tanıtılan her Thread Goster() methodunu çağracak manasına geliyor. Threadler kullanılırken aynı anda çalıştığını söylemiştik. Demekki her thread run methodunu aynı anda çalıştırabilir dolayısıyla run methodu içerisinde çağrılan Goster() methodunu da aynı anda çağrabilir manasına geliyor.


Goster() methodunun Threadler çalışırken sadece bir Thread tarafından çağrılmasını istediğimizden synchronized olarak tanıttık. Bu sayede Goster() methodu aynı anda sadece bir Thread tarafından çalıştırılacak. İlgili Threadin işi bitince diğer Thread bu methodu çalıştırıcak.


Console çıktısı:


thread1 0

thread1 1
thread1 2
thread1 3
thread1 4
thread1 5
thread2 0
thread2 1
thread2 2
thread2 3
thread2 4
thread2 5
thread3 0
thread3 1
thread3 2
thread3 3
thread3 4
thread3 5

Bu sayede ortak bir RAM alanına aynı anda birden fazla Thread'in erişmesi engellenebilir ve read/wtrite işlemi bir bir yapıldığından karışıklığın önüne geçilmiş olur.

Bir nesneye ait birden fazla synchronized tanımlanmış methodlar olabilir. Ancak aynı anda sadece bir thread bu methodlardan birine girip çalışabilir. Synchronized methodların sayısı kaç olursa olsun yanlızca tek bir method aynı anda  bu Methodlardan birinde çalışabilir. Örneğin 3 synchronized metodumuz ve bu 3 farklı metoda ayrı ayrı girmek isteyen threadlerimiz olsun. Bu threadler girmek istedikleri metodlar farklı olsa bile aynı anda çalışamayacaklar. Metodlar synchronized tanımladığından dolayı. Fakat synchronized olmayan diğer metodlarda aynı anda birden fazla thread erişimi yapılabilir.




Bir thread synchronized code içinde sleep() metodunu çağırırsa, o kod bloğuna girerken aldığı lock’ı serbest bırakmaz. Yani o kod bloğuna girmeyi bekleyen başka thread varsa, bekleyen thread lock serbest kalmadığından beklemeye devam edecektir.


Static methodlar synchronized olabilir.


Synchronized Kod Bloğu Örneği:


Aşağıdaki örnekte kodun belli bir kısmı serdar keyword'ü ile locklanmıştır. --------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;

public class MyThread implements Runnable{



    private int    end;
    private String name;
    private String keywordObj;


    public MyThread(String name, int end, String keywordObj) {

        this.end = end;
        this.name = name;
        this.keywordObj = keywordObj;

    }


    public void run() {

   
    System.out.println(name + " " +"Non-Senkronized kisim!!");
   
      synchronized(keywordObj){
             for (int i = 0; i < end; i++) {
         
            System.out.println(name + " " + i + " "+"Senkronized kisim!!");
    }
        }

   
    }


}
--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;



public class App 
{
    public static void main( String[] args )
    {
       
    Thread thread1 = new Thread(new MyThread("thread1", 6,"serdar"));
    Thread thread2 = new Thread(new MyThread("thread2", 6,"serdar"));
    Thread thread3 = new Thread(new MyThread("thread3", 6,"serdar"));
    thread1.start();
    thread2.start();
    thread3.start();
   

    }


}



--------------------------------------------------------------------------------------------------------------



Yukarıdaki örneği incelediğimizde Thread'lerin koştuğu run methodu içerisine synchronized kod bloğu olduğunu görebiliriz. Synchronized kod bloğunu keywordObj   objesine göre aynı anda tek thread çalıştırılır. Yani keywordObj objesini aynı Obje olarak kullanan thread'ler synchronized kod bloğunda tek tek çalışabilirler. Örneğe baktığımızda bu Obje String "serdar" Objesidir. Üç Thread'te bu objeyi kullanrak synchronized kod bloğunu lock eder. Bu objeye Monitor Objesi denir. 

Run() kod bloğunu incelersek synchronized kısımdan önce çalışan 
     System.out.println(name + " " +"Non-Senkronized kisim!!");
kodunu görebiliriz. Bu kod synchornized kod bloğunun içinde olmadığı için bütün thread'ler tarafından sıraya girilmeden ( herhangi bir thread bu kod parçacığını çalıştırıyor mu çalıştırmıyor mu diye bakmadan ) çalıştırılabilir ama

             System.out.println(name + " " + i + " "+"Senkronized kisim!!");
kod parçası synchronized kod bloğunun içerisinde olduğundan keyworldObj Objesine bağlı olarak sadece aynı anda tek bir Thread tarafından çalıştırılabilir. 

Console çıktısını incelersek bunu rahatlıkla görebiliriz. Kırmızı renkliler synchronized olmayan Mavi, Yeşil ve Pembe kısımlar ise synchronized kod parçacıklarının çıktısını gösteriyor. 

Bakıldığından thread1 synchronized kısımda işini bitirmeden thread2 ve thread3 bu kısma girememiş ve beklemiştir. Yani sırayla çalışmıştır aynı anda değil.


Ama synchroniz olmayan kısımlara girmişlerdir.

Yani threadleri gruplayıp ilgili thread gurubunun hangi sycnhronized kısımı tek tek çalıştırmasını ayarlıyoruz.

thread1 Non-Senkronized kisim!!

thread1 0 Senkronized kisim!!
thread1 1 Senkronized kisim!!
thread1 2 Senkronized kisim!!
thread3 Non-Senkronized kisim!!
thread2 Non-Senkronized kisim!!
thread1 3 Senkronized kisim!!
thread1 4 Senkronized kisim!!
thread1 5 Senkronized kisim!!
thread2 0 Senkronized kisim!!
thread2 1 Senkronized kisim!!
thread2 2 Senkronized kisim!!
thread2 3 Senkronized kisim!!
thread2 4 Senkronized kisim!!
thread2 5 Senkronized kisim!!
thread3 0 Senkronized kisim!!
thread3 1 Senkronized kisim!!
thread3 2 Senkronized kisim!!
thread3 3 Senkronized kisim!!
thread3 4 Senkronized kisim!!

thread3 5 Senkronized kisim!!


Synchronized methodlar synchronized keyword’ünü içerirler. Metodun hepsini değil belli bir kod parçasını kritik kod bloğu olarak tanımlamak istiyorsak yine synchronized keywordünü kullanıyoruz.

Metod üzerinden değilde belli bir kod bloğu üzerinden senkronizasyon yapıyorsak synchronized tanımı yaparken mutlaka keyword ün içine locklanacak objeninde parametre olarak verilmesi gerekmektedir.

Thread State'leri


Java threadleri her zaman bir state'tedirler. Bir thread'in takip ettiği stateler;




  • New Thread State ( Ready-to-Run state)
  • Runnable State ( Running State )
  • Blocked
  • Waiting
  • Timed Waiting
  • Terminated
New Thread State

New anahtar kelimesiyle bir Thread üretildikten sonra bu state'te olur. Ready-to-Run state. Bu state'te iken sadece start() veya stop () mthodları çağrılabilir. Başka bişeyin çağrılması IllegalThreadStateExceptin'a neden olur.


Runnable State


Start edilmiş yani Java Virtual Machine'da çalışan threadin statedir.


Blocked State


Bu state’de thread monitor lock için bekletilmiş durumdadaır. Thread bloklanmıştır. Gerekli lock açıldığında yeniden runnable state’e geçebilir.



Waiting State


Belirli bir iş parçasını yapabilmek için süresiz olarak başka threadlerin çalışmasını tamamlamasını bekleyen threadin durumu.


Timed Waiting State

Kendi işini yapabilmesi için belirli bir sure diğer threadlerin çalışmasını bekleyeme statedir.

Terminated State

Çalışması durmuş ve çıkılmış olan threadin durumu



Running durumunda olan bir thread wait(), sleep(),  join() metodları çağırılarak blocked veya waiting durumuna geçebilir.



Thread içinde sleep() metodunu kullanmamız uzun süren threadler run edilirken digger threadlerin starvation yaşamasını engeller. Sleep(milisaniye); metodu çağırıldıktan sonra içindeki bekleme değerine göre geçerli thread durdurulur o sure içinde başka thread çalıştırılmaya başlanır.



Bir thread blocked veya waiting durumuna, synchronized bir kod bloğuna girebilmek için gerekli lock’ı alamadığından dolayıda geçebilir. Synchronized tanımlanmış kod bloğu OS kavramlarından da bildiğimiz critical section kavramına denk gelir. Syncronized tanımlanmış kod bloğuna aynı anda yalnızca tek bir thread girebilir. Bu thread bu bölümünde işini bitirene kadar o bloğa girmek isteyen threadler sırada beklerler.



Sleep veya wait durumundan çıkmış veya beklediği lock  alınabilir hale gelmiş thread yeniden runnable duruma geçebilir.



Terminate edilmiş (öldürülmüş) thread yeniden start edilemez.



Sleep, Yield ve Join Metodları



Yield Methodu


Running durumda olan thread tarafından çağrıldığında, bu thread'i kendisi ile aynı öncelik seviyesine sahip bekleyen başka bir thread'i çalıştırmak için kısa süreli olarak durdurur. Ama bu durum JVM tarafından yinede garanti edilemez farklı priority e sahip bir thread de çalışabilir. Yield methodu ardından eğer bekleyen threadler kendinden düşün prioriye sahipse, bu thread çalışmaya devam eder yani  running state geçer.


Thread.yield();



Join Methodu


Eğer bir thread join () methodunu çağrırsa , o an çalışan thread join() methodunu çağıran thread'in bitmesini bekler. Join methodunu çağıran thread'in işi bittikten sonra bekleyen thread çalışmaya devam eder.


Örneğin,


t1 ve t2 threadleri çalışıyor. t1 join() methonu çağırdı. t2 methodu acilen waiting state geçer ve t1 thread'inin bitmesini bekler. Ardından yeniden running state geçer ve çalışmaya başlar.


t1.join();


Sleep Methodu


Sleep methodunu çağıran thread verilen parametre süresince uykuya alınır. Bu süre içerisinde farklı bir iş yapan thread çalıştırılabilir eğer yoksa CPU idle durumda kalır. sleep() methodu herhangi bir lock durumunu serbest bırakmadığı için, uykuya alınmış thread ile  aynı synchronized kod bloğunu çalıştırmak isteyen başka bir thread, bu sleep anında çalışmayacaktır.



sleep metodu static bir metoddur. Bir thread başka bir thread’e sleep yapacağını söyleyemez.



Thread.sleep();

--------------------------------Sleep ve Join Ornegi----------------------------------------------------



package ThreadOrnegi.com.serdar;


public class MyThread implements Runnable{



    private int    end;
    private String name;


    public MyThread(String name, int end) {

        this.end = end;
        this.name = name;

    }


    public void run() {

   
    Thread currentThread = Thread.currentThread();
   
    System.out.println(name + " " +"calismaya basladi...");
   
    try {
currentThread.sleep(4000);//4 saniye 
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
   
    System.out.println(name + " " +"4saniye sonra calismayi bitirdi...");

   

    }


}

--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;


import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class App 

{
    public static void main( String[] args )
    {
       
    Thread thread1 = new Thread(new MyThread("thread1", 6));
    Thread thread2 = new Thread(new MyThread("thread2", 6));
    Thread thread3 = new Thread(new MyThread("thread3", 6));

    thread1.start();

   
    try {
thread1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
   
   
    thread2.start();
   
    try {
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
   
    thread3.start();
   
    System.out.println("Main Thread'i bitti.");


    }


}

--------------------------------------------------------------------------------------------------------------

Console Çıktısı:



thread1 calismaya basladi...

thread1 4saniye sonra calismayi bitirdi...
thread2 calismaya basladi...
thread2 4saniye sonra calismayi bitirdi...
Main Thread'i bitti.
thread3 calismaya basladi...
thread3 4saniye sonra calismayi bitirdi...


Yukarıdaki örneği incelersek,

MyThread class'ı içerisinde 

currentThread.sleep(4000);

methodu ile şuanda açışan thread 4 saniye uykuya alınır. Uykuya alınan thread lock'larını serbest bırakarmaz yani diğer thread'ler bu lock'lara göre hareket etmek zorunda.

Main() içerisine bakacak olursak,

ilk olarak main ana thread'inden  thread1 start edilir ve join() methodu çağrılır. Join() methodu çağrıldğı için diğer Thread'ler thread1'in bitmesini bekleyecek. ( main ana thread'i dahil. )  Bitmeden çalışamayacaklar. Main ana thread'ide bu kurala dahil olarak çalışamayacak

Arkasından çağrılan thread2 methodu thread1 threadi bitince çalışacak. Çünkü main thread'i thread1 bitmeden çalışamayacak ve thread2 yi start edemeyecek. Thread1 threadi de 4 saniye boyunca uykuda olduğundan diğer threadler ( Main dahil ) 4 saniye boyunca çalışmayacaklar. 4 saniye sonra thread1 bitecek ve thread2 start çağrılacak. Ardından thread2 join() methodunu çağırdığı andan itibaren bütün thread'ler duracak. 


( bütün threadlerden kasıt o anda çalışan hepsi duracak. Kodumuza bakarsak thread1,thread2 ve thread3 ile  aynı anda sadece main çalışabiliyor ve thread1 main thread'ini join ile durduruyor. Çünkü diğerleri daha start edilemedi. )


thread2 bitince, main thread'i thread3'e start verecek. ve thread3 4 saniye boyunca uykuya alınacak. O arada thread3 join() methodunu çağırmadığından Main threadi main fonksiyonunu paralelde koşturmaya devam edecek. Bu sebepten console çıktısına bakıldığından thread3 bitmeden Main thread'i bitmiş gözüküyor.





Wait ve Notify Metodları



Java 3 tane methodu kaynakların lock'lanması ile alakalı olarak threadlerin birbirleriyle haberleşme için hizmete sunmuştur. Bu methodlar;


wait()

notify()
notifyAll()


wait() methodu


wait() methodu 3 varyansa sahiptir. Bunlardan biri diğer threadlerden notify() veya notifyAll() çağrılana kadar bekler çağrıldığında uyanır. Diğer iki varyansı ise uyanmadan önce tanımlı bir zaman kadar bekler.


Wait methodu o an çalışan thread'i başka bir thread'ten notify () veya notifyAll() methodu çağrılana kadar bekletir. Wait() methodu çağrıldığında o an alınmış bütün lock'lar hemen serbest bırakılır.


notify() methodu


notify () methodu sadece bir thread'i waiting durumundan uyandırabilir. Eğer birden fazla thread waiting durumdaysa JVM içlerinden birini sadece uyandırır. Hangi thread'in uyandırılacağı JVM ye  kalmıştır.


notifyAll() methodu


notifyAll () methodu waiting durumdaki bütün threadleri uyandırır. Hangisinin ilk uyanacağı JVM'ye bağlıdır.




wait(), notify() ve notifyAll() metodları synchronized kod bloklarının içinde çağırılmak zorundadırlar. Çünkü bu komutlar lockları serbest bırakan veya lockları threadlerin ellerine geçmesini sağlayan komutlardır.


wait() methodu ile sleep() methodu arasındaki en büyük fark, wait() methodunda elindeki lock serbest bırakılır ama sleep() methodunda locklar serbest bırakılmaz.
wait(), notify() ve notifyAll() methodları, threadler arasında paylaşılan ortak obje ( keyword ) üzerinde çağrılır. Yani synchorized keyword'ü içerisinde çağrılır.( keywordObj.wait()  
keywordObj.notify() keywordObj.notifyAll() )


Ama sleep() methodu Thread.sleep() olarak çağrılır. Aralarındaki farklardan biriside budur.


Yani wait(), notify() ve notifyAll() methodları Thread'leri kontrol ederken ortak paylaşılan obje olan synchronized objesini kullanarak birbiriyle haberleşirler. Aşağıdaki örnekte String "serdar" ortak lock objecsi üzerinden threadler haberleşmiştir.

--------------------------------------------------------------------------------------------------------------


package ThreadOrnegi.com.serdar;


public class MyWaitThread implements Runnable{


private int    end;
    private String name;
    private String keywordObjforSync;


    public MyWaitThread(String name, int end, String keywordObjforSync) {

        this.end = end;
        this.name = name;
        this.keywordObjforSync = keywordObjforSync;

    }


    public void run() {

   
        
    System.out.println(name + " " +"calismaya basladi...");
   
    synchronized (keywordObjforSync) {
   
    try {
    System.out.println(name + " " +" wait() methodu cagrildi Thread notify() bekliyor...");
    keywordObjforSync.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
   
    for(int i =0; i<end; i++)
    {
    System.out.println(name +" " + i +" " +"calisiyor...");
    }

}
   

   

    }




}

--------------------------------------------------------------------------------------------------------------

package ThreadOrnegi.com.serdar;


public class MyNotifyTread implements Runnable{


private int    end;
    private String name;
    private String keywordObjforSync;


    public MyNotifyTread(String name, int end, String keywordObjforSync) {

        this.end = end;
        this.name = name;
        this.keywordObjforSync = keywordObjforSync;

    }


    public void run() {

   
    Thread currentThread = Thread.currentThread();
   
    System.out.println(name + " " +"calismaya basladi...");
   
    synchronized (keywordObjforSync) {
   
    try {
currentThread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
   
        System.out.println(name + " " +" notify() methodu cagrildi wait threadler run mode gecirildi...");
    keywordObjforSync.notify();
   
    for(int i =0; i<end; i++)
    {
    System.out.println(name +" " + i +" " +"calisiyor...");
    }

}
   

   

    }






}

--------------------------------------------------------------------------------------------------------------
package ThreadOrnegi.com.serdar;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class App 

{
    public static void main( String[] args )
    {
       String keywordObjforSync = "serdar";//herhangi bir nesne de sync keyword olabilir.
       
    Thread thread1wait = new Thread(new MyWaitThread("thread1wait", 6 , keywordObjforSync));

    Thread thread2wait = new Thread(new MyWaitThread("thread2wait", 6, keywordObjforSync));


    Thread thread3notify = new Thread(new MyNotifyTread("thread3notify", 6, keywordObjforSync));


    thread1wait.start();       

    thread2wait.start();
    thread3notify.start();
   
    System.out.println("Main Thread'i bitti.");


    }


}


--------------------------------------------------------------------------------------------------------------

Console Çıktısı:



thread1wait calismaya basladi...

thread1wait  wait() methodu cagrildi Thread notify() bekliyor...
thread2wait calismaya basladi...
thread2wait  wait() methodu cagrildi Thread notify() bekliyor...
thread3notify calismaya basladi...
Main Thread'i bitti.
thread3notify  notify() methodu cagrildi wait threadler run mode gecirildi...
thread3notify 0 calisiyor...
thread3notify 1 calisiyor...
thread3notify 2 calisiyor...
thread3notify 3 calisiyor...
thread3notify 4 calisiyor...
thread3notify 5 calisiyor...
thread1wait 0 calisiyor...
thread1wait 1 calisiyor...
thread1wait 2 calisiyor...
thread1wait 3 calisiyor...
thread1wait 4 calisiyor...
thread1wait 5 calisiyor...


Yukarıdaki kodu ve console çıktısını inceleyecek olursak;

MyWaitThread class'ı run methodu içerisinde synchronized objesi olarak String bir nesne alıyor. Daha sonra wait() methodunu çağırararak notify bekliyor. Wait methodunu çağırdığında ise synchronized keyword objeyi lock durumuna almıştı ve bu lock'ı serbest bıraktı. Norify beklemesi demek synchronized'ta kullanılan  keyword objesinin notify edilmesini beklemek demektir. Aynı obje notify edilmelidir.

MyNotifyThread methodu ise run methodu içerisinde synchronized objesi olarak bir String nesne alıyor. Daha sonra bu synchronized keyword objesini notify ederek wait durumdaki bir Thread'i run moda geçiriyor. Notify çalıştırılmadan önce sleep koyulmasının nedeni bütün Thread'lerin start edildikten sonra  notify işlemini yapsın diye böyle bişey yaptım. Tabi bunun garantisi olmayabilir.

Kod akışına bakarsak,
main içerisinde arka arkaya thread1wait, thread2wait ve thread3notify Thread'leri start ediliyor. Bunlara synchronized keyword objesi olarak ise String "serdar" gönderiliyor. Bu durumda çalışam aşekli şöyle olması beklenir:



  • İlk olarak thread1wait çalışacak ve "serdar" keyword'ünü kullanarak synchronized kod bloğunu lock edecek. Ardından wait duruma geçerek lock'ı serbest bırakacak.
  • Lock serbest bırakılınca thread2wait çalışacak ve "serdar" keywordü ile synchronized kod bloğu bu sefer thread2wait Threadi tarafından lock edilecek. Ardından çağrılan wait methodu sonrasında ise lock serbest bırakılacak.
  • Lock serbest bırakılınca aynı synchronized keyword'ü ile thread3notify çalışır ve synchronized kod bloğunda notify() işlemini yapar. Notify işlemini yapınca bir thread wait() durumdan çıkarak run moda geçer. Console çıktısı incelenirse threadwait1 run moda geçtiğini ama thread2wait thread'inin hala wait durumda kaldığını görebiliriz.
  • Bütün thread'lerin wait modtan run moda geçebilmesi için notify() yerine notifyAll() methodu kullanılması gerekmektedir.
Önemli!!!:


JVM hangi thread'i öncelikli çalıştıracağını yada hangisi daha önce biteceğini garanti etmez. Bu sebepten notify() işlemi wait() işleminden önce olabilir. Bu durumda wait() methodunu çağıran thread'ler hep wait durumda kalır ve run moda hiç geçemezler. Bu case göz önüne alınarak işlemler yapılmalıdır. 

notifyAll() methodu çağrıldığında oluşan console çıktısı ;



keywordObjforSync.notifyAll();


Main Thread'i bitti.

thread2wait calismaya basladi...
thread2wait  wait() methodu cagrildi Thread notify() bekliyor...
thread1wait calismaya basladi...
thread1wait  wait() methodu cagrildi Thread notify() bekliyor...
thread3notify calismaya basladi...
thread3notify  notify() methodu cagrildi wait threadler run mode gecirildi...
thread3notify 0 calisiyor...
thread3notify 1 calisiyor...
thread3notify 2 calisiyor...
thread3notify 3 calisiyor...
thread3notify 4 calisiyor...
thread3notify 5 calisiyor...
thread1wait 0 calisiyor...
thread1wait 1 calisiyor...
thread1wait 2 calisiyor...
thread1wait 3 calisiyor...
thread1wait 4 calisiyor...
thread1wait 5 calisiyor...
thread2wait 0 calisiyor...
thread2wait 1 calisiyor...
thread2wait 2 calisiyor...
thread2wait 3 calisiyor...
thread2wait 4 calisiyor...

thread2wait 5 calisiyor...





Deadlocked Threads Durumu


Eğer threadler birbirlerinin ellerinde tuttukları lockları bekliyorlarsa, hiç bir zaman locklar serbest bırakılmayacak ve hiç bir thread ilerlyemeyecek böylelikle bir deadlock durumu meydana gelecek. Bu durumu ortadan kaldırmak için senkronizasyona ve kaynak paylaşımının zamanlamasına dikkat edilmelidir.



--------------------------------------------------------------------------------------------------------------


public class DeadLockExample {


String o1 = "Lock ";

String o2 = "Step ";
Thread t1 = (new Thread("Printer1") {

public void run() {

while (true) {
synchronized (o1) {
synchronized (o2) {
System.out.println(o1 + o2);
}
}
}
}
});
Thread t2 = (new Thread("Printer2") {

public void run() {

while (true) {
synchronized (o2) {
synchronized (o1) {
System.out.println(o2 + o1);
}
}
}
}
});
public static void main(String[] args) {
DeadLockExample dLock = new DeadLockExample();
dLock.t1.start();
dLock.t2.start();
}

}


--------------------------------------------------------------------------------------------------------------


Keywordlere göre lock işlemi yapıldığından  ilk olarak t1 thread'i o1     keyword'ünü lock ediyor. Aynı anda t2 thread'i ise   o2 keyword'ünü lock ediyor. Ama iki thred'inde çalışabilmesi için iki keyword'ünde lock edilmemiş sadece kendisi tarafından lock edilmiş olması gerekiyor. Bu sebepten hiç bir zaman bu  keywordlerdeki lock'lar kalkamayacak ve iki thread'te ilerleyemeyecektir.


Bu durumda deadlock oluşur. 


o1 ve o2 nesnedir.


Bu durumu ortadan kaldırmak için senkronizasyona ve kaynak paylaşımının zamanlamasına dikkat edilmelidir.









Kaynaklar:


1.) http://fatihkabakci.com/article-JAVA_THREAD_OLUSUMU

2.) https://emrahmete.wordpress.com/2011/10/06/javada-thread-yapisi-ve-kullanimi-hakkinda-ipuclari/
3.) https://gelecegiyazanlar.turkcell.com.tr/konu/android/egitim/android-101/threadler
4.) http://www.wideskills.com/java-tutorial/java-threads-tutorial
5.) http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.State.html