Ticker

6/random/ticker-posts

Java'da Thread ve Semaphore Kullanımı Bayram Harçlığı Örneği

Normal Dede, SemaphoreDede, AtomicDede ve SynchronizedDede torunlarına bayram harçlığını doğru bir şekilde dağıtabilecek mi? Bayram harçlığı örneğiyle Java'da Semaphore, Thread, AtomicInteger ve synchronized yapılarının nasıl kullanıldığını göreceğiz.

 

Osmanlı'da Bayram Kutlaması
Osmanlı'da Bayram Kutlaması

Thread Nedir?

Türkçe'mize iş parçacığı olarak geçmiş bulunan thread'ler bir hesaplama işini yürüten yapılara denir. İşlemcide yürüyen işlemlerde (process) bir veya birden fazla thread çalışabilir. Bu sayede yoğun işlem gücü gerektiren işler farklı threadlere bölünüp, işlemcinin en verimli şekilde kullanılmasını sağlayarak daha az zamanda işin bitmesine yardımcı olurlar. Örneğimizde dedelerinin elini öpüp harçlık alacak olan torunları birer Thread içinde modelleyeceğiz.

MultiThread Nedir?

Basit programlarımızı main thread dediğimiz tek bir threadde çalışacak şekilde yazarız. İşler büyüdükçe yeni threadlere ihtiyaç doğar ve çoklu threadli (multithread) yapılar ortaya çıkar. Burada bilmemiz gereken şey, kullandığımız yazılım dilinin multithread programlamaya ne kadar API desteği verdiğidir. Java multithreading ve concurrency konularında oldukça iyi standart api desteği sunuyor. 

İşlemci hızları artık Moore Yasası'na göre hızlanmıyor. İşlemci üreticileri çareyi daha çok çekirdekli işlemciler üretmekte arıyorlar. Doğal olarak yazılım dünyasının geleceği de bu çok çekirdekleri en verimli kullananların olacak. Paralel programlama gün geçtikçe daha çok önem kazanacağa benziyor.

Semaphore Nedir?

Uçak gemilerinde elinde bayraklarla pilotlara işaretler veren adamı hatırladınız mı? Kalkış Yapabilirsin! Kalkışı İptal Et! Bayrakları kullanış şekline göre farklı mesajlar verebilir. Semaphore da ismini bu bayraklı işaretlerden alıyor. Kodlar işlemcide işletilirken bir thread'e sen devam edebilirsin derken, diğerine "Hooop hemşerim nereye? Sen dur!" demek için kullanılan yapılardır, Semaphore.

Java'da Semaphore Kullanımı Örneği
Java'da Semaphore Kullanımı Örneği

Kritik Bölge Sorunu (Critical Section Problem)

Kodumuzda bir data birden fazla thread tarafından paylaşılıyorsa ve değiştirilme ihtimali varsa bu dataya kritik kaynak denir. Birden fazla threadin aynı anda bir kod bloğuna girip oradaki paylaşılan değeri değiştirmeye çalışması sonucu hatalı değerler ve öngörülemeyen sonuçlarla karşılaşabiliriz. Bu probleme kritik bölge problemi deniyor. Örneğimizde dedelerin dağıtmak üzere ellerindeki tuttukları toplam harçlık miktarı kritik kaynağımız olacak. 

Mutual Exclusion / Mutually Exclusive / Mutex Nedir?

Tanımladığımız kritik bölgelere aynı anda sedece bir threadin girebilmesi durumuna mutual exclusion denir. Java'da Semaphore tanımlanırken int permits parametresi alır. Bu kaç tane threadin acquire() çağırmasından sonra gelen threadlerin bloklanacağını bildirir. Örneğin 10 değerini verirsek kritik bölgeye girecek ilk 10 threadin devam etmesine izin verirken 11. thread geldiğinde, içerdeki 10 threadden en az birinin release() metodunu çağırmasını bekler ve 11. threadi bloklar. İçerdeki threadlerden biri release() çağırdığında artık blokaj kalkar ve bloklanan thread devam eder. Koronavirüs tedbirlerince AVM'lerde aynı anda bulunabilen müşteri sayısı kısıtına benzetebiliriz. 100 müşteri sınırı var ise, içeri giren hiç bir müşterinin çıkmadığını varsayarsak, ilk 100 müşteriye giriş izni verilir, 101. müşteri girebilmek için içerden bir müşterinin çıkmasını bekler.

Bu bilgiler göz önüne alındığında, kritik bölgeye yalnızca 1 threadin girmesini istediğimiz durumlarda Semaphore permit değerine 1 veririz. Bu özel durumdaki Semaphore nesneleri mutex olarak da adlandırılır.

synchronized Anahtar Kelimesi

Javada kritik kaynakları korumak amacıyla synchronized anahtar kelimesi kullanılır. Önüne geldiği kod bloğuna veya metoda aynı anda sadece bir threadin girmesine izin verir.

Bayram Harçlığı Dağıtma Örneği

Gelelim eğlenceli bölüme. Dedemiz ve 1000 torunu bir ramazan bayramında bayramlaşacaklar. Dedemiz, elindeki çantada toplam 100 Bin TL tutuyor ve her torununa 100 TL bayram harçlığı vermeyi planlıyor. 1000 torun aynı anda dedenin elini öpüp harçık almaya kalkarsa sonuç ne olur? Eğer kritik kaynağını, çantasını, korumaya almadıysa pek hoş şeyler olmaz tahmin edileceği üzere. 

Örnek projemizdeki sınıflara yakından bakalım.

Torun.java, dededen alacağı harçlık miktarı bilgisini ve başarılı şekilde alıp almadığı bilgisini tutuyor.

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 public class Torun {

    private Integer alinacakHarclik;
    
    private boolean harclikAlabildim;

    public Torun(Integer alinacakHarclik) {
        this.alinacakHarclik = alinacakHarclik;
    }

    public void dedeninEliniOp(IDede dede) {

        harclikAlabildim = dede.harclikVer(alinacakHarclik);
    }
    
    public boolean isHarclikAlabildim() {
        return harclikAlabildim;
    }
}

Farklı yöntemler kullanan dedeler tanımlayacağımız için şu şekilde bir IDede.java interface tanımladık.

1
2
3
4
5
6
7
8
public interface IDede {

    public boolean harclikVer(Integer alinacakHarclik);
    
    public Integer getKalanHarclik();

    void setToplamDagitilacakHarclik(Integer toplamDagitilacakHarclik);
} 

Normal Dede'miz çok iyi niyetli. Her gelen toruna sorgu sual sormadan elindekinden veriyor. Dede.java sınıfını şu şekilde tanımladık.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Dede implements IDede {

    private Integer toplamDagitilacakHarclik;

    @Override
    public boolean harclikVer(Integer alinacakHarclik) {
    
        toplamDagitilacakHarclik = toplamDagitilacakHarclik - alinacakHarclik;
       
        return toplamDagitilacakHarclik >= 0;
    }
    }

AtomicDede ise elindeki toplam harçlık miktarını AtomicInteger nesnesi kullanarak tutuyor. AtomicInteger sınıfı, üzerindeki eksiltme arttırma gibi işlemleri atomic olarak halledebiliyor ve araya başka thread girmiyor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class AtomicDede implements IDede {

    private AtomicInteger toplamDagitilacakHarclik;

    @Override
    public boolean harclikVer(Integer alinacakHarclik) {
         
        toplamDagitilacakHarclik.addAndGet(-alinacakHarclik);
       
        return toplamDagitilacakHarclik.get() >= 0;
    }
}   

SemaphoreDede harçlık verme işini bir kabinde yapıyor ve o kabine aynı anda sadece bir torunu alıyor. Kullandığı Semaphore mutex kilidini kullanarak, harclikVer metoduna ilk giren threadle birlikte acquire() metodu ile Semaphore'daki izin miktarını 1 eksiltiyor. Ve bu ilk giren thread işini bitirene, çıkarken de release() metodunu çağırana kadar da içeriye başka thread almıyor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SemaphoreDede implements IDede {

    private Integer toplamDagitilacakHarclik;
    
    private Semaphore mutex = new Semaphore(1);

    @Override
    public boolean harclikVer(Integer alinacakHarclik) {
       
        try {
            mutex.acquire();
           
            toplamDagitilacakHarclik = toplamDagitilacakHarclik - alinacakHarclik;
           
            mutex.release();
        } catch (InterruptedException e) {

            e.printStackTrace();
        }
       
        return toplamDagitilacakHarclik >= 0;
    }

}

Örnek projemizde ElOpmeHarclikAlmaThread.java ismindeki sınıf ile torunun dedenin elini öpmesini simüle edeceğiz.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class ElOpmeHarclikAlmaThread  extends Thread {

    private IDede dede;
    
    private Torun torun;
    
    public ElOpmeHarclikAlmaThread(IDede dede, Torun torun) {
        this.dede = dede;
        this.torun = torun;
    }

    @Override
    public void run() {

        torun.dedeninEliniOp(dede);
    }

}

BayramKutlama.java sınıfımızda da simülasyon kodlarımız bulunuyor. Torun sayısı kadar Thread oluşturuyor ve başlatıyoruz.Sonra tüm threadlerin bitmesini join() metodu ile bekliyoruz. En sonunda dedenin elinde kalan para miktarının sıfır olmasını bekliyoruz. Sıfır değil ise işler karışmış demektir.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class BayramKutlama {
    
    public void bayraminizKutluOlsun(IDede dede) {
        
        List<ElOpmeHarclikAlmaThread> elOpmeThreadleri = new ArrayList<>();
       
        dede.setToplamDagitilacakHarclik(100000);
       
        int torunSayisi = 1000;
               
        int harclikMiktari = 100;
       
        for(int i = 0; i < torunSayisi; i++) {
           
            Torun torun = new Torun(harclikMiktari);
           
            ElOpmeHarclikAlmaThread elOpmeThread = new ElOpmeHarclikAlmaThread(dede, torun);
           
            elOpmeThreadleri.add(elOpmeThread);
        }
       
       
        for(int i = 0; i < torunSayisi; i++) {
       
            elOpmeThreadleri.get(i).start();
        }
       
       
        // Tum threadlerin bitmesini bekle
        for(int i = 0; i < torunSayisi; i++) {
           
            try {
                elOpmeThreadleri.get(i).join();
               
                if(!elOpmeThreadleri.get(i).getTorun().isHarclikAlabildim()) {
                   
                    System.out.println( (i + 1) + ". Torun harcligini tam alamamis!!");
                }
               
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(dede.getClass().getSimpleName() + " Bitirdi. Elinde kalan miktar " + dede.getKalanHarclik());
        if(dede.getKalanHarclik() > 0) {
           
            System.out.println(dede.getClass().getSimpleName() + " BASARISIZ!!!");
           
        } else {
           
            System.out.println("Dedenin elinde hic para kalmadi. Berhudar olsunlar!");
        }
    }
}

BayramApp.java sınıfında da tüm dede tiplerimiz için simülasyonumuzu koşturuyoruz. Sonuçları konsola yazdırıyoruz. Üst üste çalıştırarak sonuçların hep aynı olduğunu görüyoruz. Sadece normal Dede sınıfımızda dedemizin elinde kalan miktar sıfırdan farklı oluyor. Diğer dedelerimiz kullandıkları yöntemlerle kritik kaynaklarını korumanın bir yolunu bulmuş görünüyorlar.

Bayram Harçlığı Output
Bayram Harçlığı Output

 

Output:

Dedeler harcliklari dagittiginde ellerinde hic para kalmamasi lazim!
--------
Basliyoruz
--------
Dede Bitirdi. Elinde kalan miktar 2700
Dede BASARISIZ!!!
Dede hesaplama suresi 54

AtomicDede Bitirdi. Elinde kalan miktar 0
Dedenin elinde hic para kalmadi. Berhudar olsunlar!
AtomicDede hesaplama suresi 49

SynchronizedDede Bitirdi. Elinde kalan miktar 0
Dedenin elinde hic para kalmadi. Berhudar olsunlar!
SynchronizedDede hesaplama suresi 51

SemaphoreDede Bitirdi. Elinde kalan miktar 0
Dedenin elinde hic para kalmadi. Berhudar olsunlar!
SemaphoreDede hesaplama suresi 50

Örnek projemizi GitHub'da şuradan indirip kendiniz de deneyebilirsiniz.



Yorum Gönder

1 Yorumlar

  1. Bende mi görünmedi bilemedim ama kodları markdown code block gibi bir şeyin içinde yapsanız tadından yenmez, güzel anlatım olmuş elinize sağlık.

    YanıtlaSil