Singleton pattern kullanım amacı bir sınıfın kısıtlı nesnesini (hatta çoğunlukla tek) tek bir yerden yaratılması amacıyla kullanılmaktadır. Şimdi bunu sağlayan Singleton sınıfı kodunu görelim. (Kod 1)
public class Singleton {
private static Singleton theInstance = new Singleton();
// Sınıf yüklendiğinde yaratılan tekil nesne
public static Singleton getInstance() {
// Dışarıdan nesne almak için bu metodu kullanacağız.
return theInstance;
}
private Singleton() {
// Constructor private tanımlansın ki dışarıdan erişilemesin.
System.out.println("constructor");
//Constructor 100 ms. bekleme gibi bir "iş" yapsın.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Burada sınıfın nesnesine sadece getInstance() metodu ile erişilebilecektir. Ccnstructor 100 ms. gibi “makul” bir süre beklemektedir. Bu sınıftan nesneleri yaratan sınıfımızın kodunu da inceleyelim. (Kod 2)
public class SingletonTest {
private static Singleton singletona, singletonb;
public static void main(String[] args) {
singletona = Singleton.getInstance();
singletonb = Singleton.getInstance();
System.out.println(singletona == singletonb);
}
}
SingletonTest sınıfını çalıştırdığımızda aşağıdaki gibi bir çıktı vermektedir.
constructor
true
Görüldüğü üzere kodumuz amacına ulaşmış constructor 1 defa çağırılmıştır. Ancak bu yöntemde Singleton sınıfının nesnesi sınıf yüklendiği anda ilklenmektedir. Şimdi bu ilklemeyi ihtiyaç anına bırakalım (Lazy initilization). Yeni Singleton sınıfımız şu şekilde oluşacaktır. (Kod 3)
public class Singleton {
private static Singleton theInstance;
// İhtiyaç anında yaratılacak nesne
public static Singleton getInstance() {
// Dışarıdan nesne almak için bu metodu kullanacağız.
// Eğer nesnemiz yaratılmamış ise yaratalım.
if (theInstance == null) {
theInstance = new Singleton();
}
// Şu an ya da önceden yaratılan nesneyi döndürelim.
return theInstance;
}
private Singleton() {
// Constructor private tanımlansın ki dışarıdan erişilemesin.
System.out.println("constructor");
// Constructor 100 ms. bekleme gibi bir "iş" yapsın.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Bu şekilde de programımızı çalıştırdığımızda aynı çıktıyı vermekte ve beklenen şekilde işlemektedir. Şimdi SingletonTest sınıfını biraz değiştirelim ve getInstance() birden çok threadden çağırıldığında ne yapıyor onu görelim. (Kod 4)
public class SingletonTest {
private static Singleton singletona, singletonb;
public static void main(String[] args) {
// getInstance() iki ayrı thread ile çağırılsın.
Thread threada = new Thread() {
@Override
public void run() {
singletona = Singleton.getInstance();
}
};
threada.start();
Thread threadb = new Thread() {
@Override
public void run() {
singletonb = Singleton.getInstance();
}
};
threadb.start();
// Her iki thread tamamlanana kadar beklensin
while (!(threada.getState() == Thread.State.TERMINATED && threadb
.getState() == Thread.State.TERMINATED))
;
System.out.println(singletona == singletonb);
}
}
Çıktımıza göre buu sefer sapıttı sanırım.
constructor
constructor
false
Evet constructor iki defa işledi ve bize iki ayrı nesne üretti. Şimdi ilklendirmemizi thread-safe hale getirelim. Bunu da getInstance() metodunu synchronized olarak tanımlayarak yapacağız. (Kod 5)
public class Singleton {
private static Singleton theInstance;
// İhtiyaç anında yaratılacak nesne
public static synchronized Singleton getInstance() {
// Dışarıdan nesne almak için bu metodu kullanacağız.
// Eğer nesnemiz yaratılmamış ise yaratalım.
// Metodu synchronized yaparak sayesinde aynı anda sadece bir yerden
// çağırılmasını garanti ettik.
if (theInstance == null) {
theInstance = new Singleton();
}
// Şu an ya da önceden yaratılan nesneyi döndürelim.
return theInstance;
}
private Singleton() {
// Constructor private tanımlansın ki dışarıdan erişilemesin.
System.out.println("constructor");
// Constructor 100 ms. bekleme gibi bir "iş" yapsın.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Evet! Eski çıktımıza geri döndük. Ancak unutmayalım ki synchronized Java’nın bize sağladığı bir imkan onun için Wikipedia Singleton pattern başlığında gördüğüm yöntemle devam edeceğiz. Bu yöntemde ilk koddaki gibi ilkleme sınıf yükleme esnasında ancak yardımcı başka bir sınıfın yüklenmesinde gerçekleşecek. (Kod 6)
public class Singleton {
private static class SingletonHolder {
// İlklemeyi ilk örnekteki gibi sınıf yüklemesi esnasına taşıyacağız.
// Ancak bu sefer ilklemeyi Singleton değil yardımcı başka bir sınıf
// gerçekleştirecek.
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
// Bu kullanım SingletonHolder sınıfının yüklenmesini dolayısıyla
// Singleton nesnesinin ilklenmesini tetikleyecek.
return SingletonHolder.INSTANCE;
}
private Singleton() {
// Constructor private tanımlansın ki dışarıdan erişilemesin.
System.out.println("constructor");
// Constructor 100 ms. bekleme gibi bir "iş" yapsın.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}