Pure Java – 88 Thread – 05

Merhaba Arkadaslar
Bu bolumde Thread Interference , Memory Consistency Error ve Thread Safe & Shared Resources konusunu inceleyecegiz

Simdi asagidaki kucuk kod ornegimiz uzerinden kotu bir senaryo dusunelim ;

public class Counter {
    private  long count = 0;
    public void add(long value){
        this.count = this.count + value;
    }
}

A ve B olmak uzere 2 tane thread oldugunu dusunelim. Burada value degeri 2 ve 3 olarak deger geldigini varsayalim;

this.count = 0 degerine sahiptir.
A threadi register(hafiza)’dan degeri okur. (this.count = 0)
B threadi register(hafiza)’dan degeri okur. (this.count = 0)
A thread’i 2 degerini ekler.
A thread’i 2 degerini register’a yazar. this.count = 2 dir.
B thread’i zaten degeri okumustu bu noktada 3 degeri ekler.
B thread’i register’a 3 degerini yazar. this.count = 3 oldu.

this.count degerini 5 beklerken 3 oldu ! Peki neden boyle oldu ?

Birden fazla thread , “ayni kaynaga” ulastiginda (read) problem olmaz.
Problem teskil edecek nokta ; birden fazla thread “ayni kaynaga”(instance variable), “ayni obje referansi” uzerinden ulasip degistirmeye calistiginda(write) ortaya cikabilir. Bu probleme Race Condition adi verilir. Local degiskenerin durumu ve instance degiskenler yazinin sonunda tekrar ele alinacaktir.

Running durumunda bir thread , Thread Scheduler tarafindan heran calismasi runnable duruma gecebilir bir baska thread running duruma gecebilir.
Bu nedenle hangi thread’in “paylasilan kaynaga” ulasmaya calistigini bilemeyiz.

Thread Interference(girisim/karisma/karisim) , ayni ‘data’ uzerinde farkli threadler araliklarla(interleave) calistigi durumda ortaya cikabilir.

Benzer bir ornek olarak ;

c++ ifadesi(statement) icin su adimlar yapilir;
c degerinin mevcut degeri alinir
c degeri bir(1) artirilir. (c– , icin 1 azaltilir)
c degeri kaydedilir(store)

Thread A : c degiskenin degerini alir.
Thread B : c degiskenin degerini alir.
Thread A : c degiskeninin degerini arttirir. c = 1
Thread B : c degiskeninin degerini azaltir. c = -1
Thread A : c degiskeninin degerini kaydeder(store) c = 1
Thread B : c degiskeninin degerini kaydeder(store) c = -1

Burada increment ve decrement metotlarindan sonra beklenen deger 0 olmalidir , fakat Thread A’nin c degiskenin degerini 1 yapmasi Thread B tarafindan overwrite edildi. Farkli durumlarda da tam tersi bir durum olabilir.

Bu ornek cogu zaman dogru calisacaktir, fakat bunun bir garantisi yoktur.

class Counter implements Runnable {

	@Override
	public void run() {
		increment();
		decrement();
	}

	int c = 0;

	public void increment() {
		c++;
	}

	public void decrement() {
		c--;
	}

	public int value() {
		return c;
	}

}

class CounterTest {

	public static void main(String[] args) {

		Counter counter = new Counter();

		Thread t1 = new Thread(counter);
		Thread t2 = new Thread(counter);

		t1.start();
		t2.start();

		System.out.println(counter.c);

	}

}

Bir baska problem olarak Memory Consistency Error karsimiza cikmaktadir.

int counter = 0;
public void increment() {
    counter++;
    System.out.println(counter);
}

counter degiskeni bir artirildiktan sonra consola degeri basilmakta. Eger bu iki ifadeyi(statement) ayni thread calistiracaksa problem yok fakat counter++ ifadesini ayri thread console basilmasini ayri thread yapacak olursa bu durumda ekrana 0 basma ihtimali vardir.

Thread A’daki bu degisimin Thread B tarafindan gorunur/visible olmasinin bir garantisi yoktur.

Farkli threadler ayni ‘data’ uzerinde tutarsiz oldugu durumda Memory Consistency Error ortaya cikar.

Thread Safe & Shared Resources
Thread safe ; birden fazla thread ayni anda/es zamanli olarak/simutaneously metoda erismeye calistiginda problem olmamasi anlamini tasir. Burada problemden kasit ; race condition , deadlock , memory consistency error gibi problemlerdir.

Local Variables
Local Variables , thread-safe ozellige sahiptir. Her thread’in kendi stack alaninda local degiskenler yasarlar. Bunun anlami local degiskenler thread’ler arasinda paylasilmazlar.

public void test(){
   int threadSafeLocalVariable = 0;
   threadSafeLocalVariable++;
}

Local Object References
Local degiskenler primitive ya da reference type olsun Stack’te yasarlar. Objeler ise Heap’te yasar. Local Object Reference’in kendisi Stack’te yasar. Bu reference degiskenin gosterdigi obje ise Heap’te yasar.

Bir obje local olarak kullaniliyorsa yani metot disarisinda cikmazsa bu durumda thread safe ozellige sahiptir.

	public void testMethod() {

		LocalObject localObject = new LocalObject();

		localObject.callMethod();
		testMethod2(localObject);
	}

testMethod’u inceledigimizde , calisan her thread kendi LocalObject tipinde objesini olusturacaktir ve localObject reference degiskeni bu objeyi gosterecektir. Burada oldugu gibi kullanim sekli LocalObject objesini thread-safe yapacaktir. Yani objenin method icerisinde olusmasi ve geri dondurulmemesi(void) , localObject referans degiskeni uzerinden metod cagrilmasi problem teskil etmez.

Reference type degiskenin metot parametresi olarak kullanildigi durumda metodun thread-safe ozelligi ortadan kalkar.
Birden fazla thread testMethod2 “ayni” obje referansi uzerinden testMethod2’ye ulasirsa bu durumda ayni objeyi birden fazla thread paylasacaktir. Burada her thread icin bir obje olusmaz!!.

Burada birden fazla thread calissa bile sadece bir tane LocalObject tipinde obje olusacaktir. Her thread icin bir obje olusmadigi icin yani obje threadler arasinda paylasildigi icin thread-safe durumu ortadan kalkar.

	public void testMethod2(LocalObject localObject) {
		localObject.setValue("value");
         ....
	}


Object Members

Yazinin basinda inceledigimiz ornekler Object Member yani instance degiskenlerin thread safe olmamasindan kaynaklanan problemlerle ilgiliydi.

Instance degiskenler obje ile birlikte heap’te yasarlar. Bu nedenle eger birden fazla thread “ayni obje” refansi uzerinden bir metot cagirirsa ve bu metot icerisinde instance degiskenin degeri degistirilirse bu durumda bu metot thread-safe degildir.

public class NotThreadSafe {
	StringBuilder builder = new StringBuilder();

	public void add(String text) {
		this.builder.append(text);
	}

	private long count = 0;

	public void add(long value) {
		this.count = this.count + value;
	}

}

Birden fazla thread add metodunu “ayni NotThreadSafe objesi” uzerinden ayni anda/es zamanli olarak/simultaneously cagirirsa burada race condition durumu ortaya cikar.

public class NotThreadSafe {
	StringBuilder builder = new StringBuilder();

	public static void main(String[] args) {
		NotThreadSafe sharedInstance = new NotThreadSafe();

		new Thread(new MyRunnable(sharedInstance)).start();
		new Thread(new MyRunnable(sharedInstance)).start();

	}

	public void add(String text) {
		this.builder.append(text);
	}

}

class MyRunnable implements Runnable {
	NotThreadSafe instance = null;

	public MyRunnable(NotThreadSafe instance) {
		this.instance = instance;
	}

	public void run() {
		this.instance.add("some text");
	}
}

Bununla birlikte birden fazla thread add metodunu “farkli NotThreadSafe “ objesi uzerinden ayni anda/es zamanli olarak/simultaneously cagirirsa burada race condition durumu ortaya cikmaz.

....
	public static void main(String[] args) {
		NotThreadSafe sharedInstance1 = new NotThreadSafe();
		NotThreadSafe sharedInstance2 = new NotThreadSafe();

		new Thread(new MyRunnable(sharedInstance1)).start();
		new Thread(new MyRunnable(sharedInstance2)).start();
	}
....

Burada her thread kendi NotThreadSafe objesine sahiptir. NotThreadSafe objesi paylasilmadigi icin thread’lerin birbirine mudahale etmesi/karismasi(interfere) mumkun degildir. Burada race condition durumu ortaya cikmayacaktir.

Yazimi burada sonlandiriyorum.
Herkese Bol Javali Gunler dilerim.
Be an oracle man , import java.*;
Levent Erguder
OCP, Java SE 6 Programmer
OCE, Java EE 6 Web Component Developer

Print Friendly, PDF & Email

Leave a Reply

Your email address will not be published. Required fields are marked *