Yazan : Şadi Evren ŞEKER

JAVA, C++ veya C# gibi nesne yönelimli programlama dillerinde kullanılan bir terimdir. Basitçe, aynı anda çalışan birden fazla lifin (thread) veya işlemin (process) sıralı olmasını ve birbiri ile iletişim halinde çalışmasını sağlar.

Nesne yönelimli programlama ortamında iki farklı kavram birbirine sıkça karışmaktadır. Aslında anlam olarak birbirine yakın olan synchronized methods (eşlemeli metotlar, synchronous method) ve synchronized statements (eşlemeli satırlar) kullanımda ufak farklılıklara sahiptir.

Bir metodun eşlemeli olması durumunda metottaki bütün işlemler, bu metodu çağıran lifler (threads) tarafından sırayla yapılır. Yani bir lif (thread) bu metodu çalıştırırken bir diğeri beklemek zorundadır.

Benzer şekilde eşlemeli satırlarda ise kritik alan (critical section) ismi verilen bir veya daha fazla satırdan oluşan bir blok olur. Bu blok eşlemeli metotlarda olduğu gibi liflerin (threads) sırayla buradaki komutları çalıştırmasını gerektirir.

Öncelikle JAVA dilinde metotların nasıl eşlemli (synchronized) yapıldığına bakalım:

public class Sayac {
    private int c = 0;
    public void arttir() {
        c++;
    }
    public void azalt() {
        c--;
    }
    public int deger() {
        return c;
    }
}

Yukarıdaki kodda bir sınıf tanımlanmış ve c isminde bir int değişken bu sınıf (class) içerisinde bir sınıf değişkeni (class variable) olarak tanımlanmıştır. Yukarıdaki bu sınıfı kullanan bütün lifler (threads) bu değişkene erişme ve değiştirme hakkına sahiptir. Örneğin iki farklı lif (thread) aynı anda arttir() metodunu çağırırsa ve c değişkeninin ilk değeri 0 ise, değişken iki kere arttırılarak 2 olabilir veya iki lif (thread) tarafından da birer kere arttırılarak 1 olabilir.

Yani iki lif (thread) de aynı anda değişkenin değerini aldılar (0 olarak) arttırdılar (1 oldu) ve aynı anda değişkenin içine yazdılar. Dolayısıyla değişken iki farklı lif (thread) tarafından arttırılmasına rağmen 1 değerine sahip olmuş olabilir. Bu problem bilgisayar bilimlerinde sıkça karşılaşılan get and set (alma ve koyma) problemidir ve bu iki işlemin bölünemez (atomic) olmamasından kaynaklanır.

Bu yazı şadi evren şeker tarafından yazılmış ve bilgisayarkavramlari.com sitesinde yayınlanmıştır. Bu içeriğin kopyalanması veya farklı bir sitede yayınlanması hırsızlıktır ve telif hakları yasası gereği suçtur.

Yukarıdaki bu klasik problemin çözümü fonksiyona bir lif (thread) erişirken başka birisinin erişmemesidir. Böylelikle fonksiyona erişen her lif (thread) mutlaka değeri bir arttıracak ve sonuç olarak c değişkeninin değeri tam olarak bilinebilecektir.

Yukarıdaki bu eşlemeli erişimi sağlamak için kodun aşağıdaki şekilde değiştirilmesi gerekir:

public class synchronizedSayac {
    private int c = 0;
    public void synchronized arttir() {
        c++;
    }
    public void synchronized azalt() {
        c--;
    }
    public int deger() {
        return c;
    }
}

Yukarıdaki yeni kodda dikkat edileceği üzere metotlar synchronized kelimesi ile başlamakta ve dolayısıyla erişim anlık olarak tek life (thread) izin vermektedir.

Yukarıdaki kodda deger() fonkisyonu synchronized yapılmamıştır. İlk bakışta bu durum doğru gibi gelir. Genelde şu hatalı kanı yaygındır: Bunun sebebi bu fonksiyonun içeriğinde karışıklık sebebi olacak bir iş yapılmamasıdır. Yani anlık olarak değişkenin değerinin okunması mümkündür ve bu okuma işlemi diğer liflerde (threads) bir probleme yol açmaz.

Bu kanı doğrudur ve gerçektende diğer liflerde (threads) bir soruna yol açmaz ancak deger() metodunu çağıran lif(thread) için sorun vardır. Yani o anda bir lifin (thread) arttir() fonksiyonuna eriştiğini ve bizim de deger() fonksiyonuna eriştiğimizi düşünelim. Acaba deger() fonksiyonu arttırılmadan önceki değerimi arttırıldıktan sonraki değerimi döndürecek?

İşte bu yüzden bu fonksiyonu da eşlemeli yapmak gerekir ve kodun doğru hali aşağıdaki şekildedir:

public class synchronizedSayac {
    private int c = 0;
    public void synchronized arttir() {
        c++;
    }
    public void synchronized azalt() {
        c--;
    }
    public int synchronized deger() {
        return c;
    }
}

Yapıcılar (constructors) eşlemeli olamaz. Yani bir yapıcı fonksiyonunun (constructor) başına synchronized kelimesi yazılması hatadır. Bu gayet açıktır çünkü zaten bir yapıcıya (constructor) anlık olarak tek lif (thread) erişebilmektedir.

C# dilinde senkron metotlar için sadece yazılış farklılığı vardır. Örneğin yukarıdaki JAVA koduyla aynı işi yapan csharp kodu aşağıdaki şekildedir:

using	System;
using	System.Runtime.CompilerServices;
public class synchronizedSayac {
    private int c = 0;
[MethodImpl(MethodImplOptions.Synchronized)]
 public void arttir() {
        c++;
    }
[MethodImpl(MethodImplOptions.Synchronized)]
 public void azalt() {
        c--;
    }
[MethodImpl(MethodImplOptions.Synchronized)]
 public int deger() {
        return c;
    }
}

Görüldüğü üzere metodun başındaki synchronized terimi yerine [MethodImpl(MethodImplOptions.Synchronized)] gelmekte ve ilave olarak System.Runtime.CompilerServices paketi programa dahil edilmelidir.

Eşlemeli satırlar

Programlama dillerinde bazen bir metodun tamamına değil de sadece bir veya birkaç satırı içeren bir bloğa anlık olarak tek bir lifin (thread) girmesi istenebilir. Bu durumda ilgili bu satırların eşlemeli (synchronized) yapılması yeterlidir. Bunun için JAVA dilinde, aşağıdakine benzer bir kodlama gerekir:

public void isimEkle(String isim) {
    synchronized(this) {
          sayac++;
    }
    isimListesi.add(isim);
}

Yukarıdaki kodda görüldüğü üzere isimListesi isimli bir listeye yada vektöre (vector) isim eklenmektedir. Bu işlemin sıralı olması gerekmez. Yani listeye her lif(thread) değişik zamanlarda ekleme yapabilir. Ancak eklenen isimlerin sayısının doğru tutulması açısından sayacın arttırılma işlemine erişen liflerin (thread) aynı anda değiştirme yapmaması gerekir. Bunun için sadece bu satırı eşlemeli (synchronized) yapan kod eklenmiştir.

Aynı kod C# için aşağıdaki şekilde yazılabilir:

public void isimEkle(String isim) {
    lock(this) {
          sayac++;
    }
    isimListesi.add(isim);
}

Görüldüğü üzere iki dil arasındaki tek fark, synchronized terimi yerine lock teriminin kullanılmasıdır.

Yorumlar

  1. Abdulkadir

    Hocam gercektende cok guzel bir calisma olmus bu konuyu cok sik kullanmadigimdan unutuyorum ve sizin yaziniz okuyunca tum bilgilerim canlanmiyor emeyinize saglik Hayirli gunler

  2. Uğur

    Öncelikle yazınız için teşekkürler

    synchronized(this) {….} satır eşleme işleminde , this anahtar kelimesinin görevi nedir , incelediğim bir başka kodda this yerine {… } içinde de kullanılan bir class variable yazılmış ; sebebini söyleyebilir misiniz …

  3. Şadi Evren ŞEKER Article Author

    This kelimesi, yukarıdaki kodda, kısaca, sınıftaki ilgili kod bloğuna (synchronized kısmı) erişimin sıraya sokulmasını ve anlık olarak o sınıftan üretilen bütün nesneler arasında erişimin anlık olarak tek bir sınıf tarafından yapılmasını sağlamaktadır. Yani o sınıftan ileride türeyecek olan nesnelerin isimlerini bilmediğimiz için this kelimesi kullanılmıştır.

    This kelimesi ile ilgili daha fazla bilgiyi aşağıdaki yazıdaki yorumlarda açıklamıştım:

    http://www.bilgisayarkavramlari.com/2008/11/24/yapici-constructor/

    Synchronized kelimesinin JAVA’daki kullanımına baktığınızda bir parametre alır ve bu parametreye göre ilgili bloğun erişimini sıraya sokar. Buradaki parametre kilit (lock) ismi olabilir. Yani yukarıdaki örneklerde de aslında verilen this parametresi JAVA tarafından

    this.getClass()

    fonksiyonu çağrılarak dönen değeri bir kilit (lock) olarak tutmaktadır.

    Aslında bu durum nispeten büyük projelerde tartışmalı bazı problemlere sebep olmaktadır. Örneğin tamamen farklı amaçla kilit (lock) kullanılması istendiğinde ve başka birisi sınıf ismini bir kilit olarak kullandığında problemler olabilmektedir.

    Bu yüzden kilitlerin içsel (implicit) kullanılması daha doğrudur. Yani yukarıdaki yazıda bulunan

    synchronized arttir()

    şeklinde fonksiyonlara eklenmesi daha doğrudur. Şayet dışsal olarak (explicit)

    synchronized (this)

    kullanımı tercih edilirse, karmaşık eşlemeli işlemlerde problem yaşanabilir.

  4. Ömer KANAR

    Çok güzel bir çalışma olmuş.Hem java hem de C# ‘a değinilmiş olması ayrı bir güzellik katmış.
    Peki sadece aynı değerlere sahip metot erişimlerini sıraya koymak istesem nasıl bir yapı kurarım?

Ömer KANAR için bir cevap yazın Cevabı iptal et

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir