10 Şubat 2023 Cuma

Double Checked Locking Singleton

Giriş
Eğer multi thread ortamda lazy singleton kullanmak istersek şöyle yaparız. Nesne volatile tanımlanmalıdır! Volatile tanımlanınca "partially initialized object" sıkıntısı olmuyor. Yani 
single == null kontrolü ve single = new ... satırları yer değiştirmiyor.

Örnek
Şöyle yaparız
public class Singleton {
  private static volatile Singleton _instance; // volatile variable
  public static Singleton getInstance() {
    if (_instance == null) {
      synchronized (Singleton.class) {
        if (_instance == null)
          _instance = new Singleton();
      }
    }
    return _instance;
  }
}
Örnek
Şöyle yaparız. Nesne volatile tanımlanmalıdır! Volatile tanımlanınca "partially initialized object" sıkıntısı olmuyor. Yani 
single == null kontrolü ve single = new ... satırları yer değiştirmiyor.
class MultithreadedSingle {        
  private static volatile MultithreadedSingle single;       
  private MultithreadedSingle() {
  }        
  public static MultithreadedSingle getInstance() {
    if(single==null){
      synchronized(MultithreadedSingle.class){
        if(single==null){
          single= new MultithreadedSingle(); 
        }      
      }
    }
    return single;
  }
}
Eğer lock olarak sınıfı kullanmak istemezsek şöyle yaparız.
private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}
Şimdi niçin synhronized kelimesinden sonra ikinci defa null kontrolü yaptığımıza bakalım.
if (_instance == null) {                // L1
    synchronized (Singleton.class) {    // L2
        if (_instance == null) {        // L3
            _instance = new Singleton();// L4
        }
    }
}
Eğer ikinci defa null kontrolü yapmazsak iki thread şöyle çalışır. Yani bloke olan ikinci thread, synchronized bloğa girince, birinci thread'in yaptığı işi kontrol etmezse, yeni bir nesne yaratır
1. Thread 1 L1'e gelir ve _instance null'dır
2. Thread 2 L1'e gelir ve _instance null'dır
3. Thread 1 L2'de mutex'i kilitler
4. Thread 2 L2'de mutex'i kilitlemeye çalışır ama bloke olur
5. Thread 1 yeni bir instance yaratır ve L4'te atar
6. Thread 1 L2'de kilitlediği mutex'i bırakır
7. Thread 2 L2'de mutex'i kilitler
8. Thread 2 yeni bir instance yaratır ve L4'te atar
9. Thread 2 L2'de kilitlediği mutex'i bırakır
Dolayısıyla hata olur. synchronized kelimesinden sonra ikinci null kontrolü Thread 2'nin yeni bir instance yaratmasını engeller. İşte bu yüzden ismi "Double Checked"

Yanlış bir kod şöyle
class SharedObject {

  private static final Object LOCK = new Object(); //Volatile değil

  private static SharedObject instance = null;

  public static SharedObject retrieve() {
    if (instance == null) {
      synchronized (LOCK) {
        //Burada ikinci null kontrolü yapılmıyor
        instance = create();
      }  
    } 
    return instance;
  }
   
  private static SharedObject create() {
    return new SharedObject();
  }
}

Hiç yorum yok:

Yorum Gönder