Singleton Pattern

Singleton Pattern က creational design pattern တစ်ခု ဖြစ်ပါတယ်။ Developer တော်တော်များများ လည်း အသုံးပြုကြပါတယ်။ Singleton ကတော့ ရိုးရှင်းပါတယ်။ Class က instance တစ်ခု ပဲ ဖန်တီးထားပြီး အဲဒီ instance ကို ပဲ အမြဲ သုံးနေဖို့ပါပဲ။

ဥပမာ User login ဝင်ပြီးသွားရင် CurrentUser instance ကို အမြဲခေါ်ပြီး user name, user token တွေ ရယူ နေသလိုပေါ့။

Singleton ကို Database Connection Pool တွေမှာလည်း အသုံးများပါတယ်။ DB connection ဟာ ၂ ခါ ၃ ခါ ထပ်ဆောက်နေဖို့ မလိုပါဘူး။ တစ်ကြိမ် ဆောက်ပြီးသွားရင် ကြိုက်သည့် class ကနေ ခေါ်ပြီး အသုံးပြုနိုင်ပါတယ်။

public class ConnectionPool {
    private static ConnectionPool pool = new ConnectionPool();
    private Connection connection = new Connection();

    private ConnectionPool() {}
    
    public static ConnectionPool getInstance() { 
        return pool;
   }
    
    public Connection getConnection(){
        return connection; 
    }
}

ဒီ class မှာ ဆိုရင်

private static ConnectionPool pool = new ConnectionPool();

ConnectionPool ကို static ကြေငြာထားပြီးတော့

public static ConnectionPool getInstance() { 
        return pool;
}

getInstance() static function ကနေ ယူ သုံးထားတာ တွေ့နိုင်ပါတယ်။ တနည်းပြောရင် ConnectionPool.getInstance() ဆိုရင် ဖန်တီးထားပြီး ဖြစ်သည် pool ကို ပဲ return ပြန်ပါလိမ့်မယ်။


ဒီ code ရဲ့ ပြဿနာက pool က သုံးသည် ဖြစ်စေ မသုံးသည် ဖြစ်စေ memory ပေါ်မှာ နေရာ ယူထားတာပါ။ တကယ်ကို သုံးသည့် အခါမှာ connection pool ကို ဆောက်ချင်သည့် အခါမှာ code ကို အောက်ပါအတိုင်း အနည်းငယ် ပြင်ပါမယ်။

public class ConnectionPool {
    private static ConnectionPool pool;
    private Connection connection = new Connection();

    private ConnectionPool() {}
    
    public static ConnectionPool getInstance() { 
        if (pool == null) {
            pool = new ConnectionPool();
        }
        return pool;
   }
    
    public Connection getConnection(){
        return connection; 
    }
}

ဒါဆိုရင် pool က null ဖြစ်နေသည့် အချိန်မှ connection pool အသစ် တစ်ခု ကို ဆောက်ပါလိမ့်မယ်။

ဒီ code မှာ နောက် ထပ် issue က

private ConnectionPool() {}

constructor က ဘာမှ မထည့်ထားသည့် အတွက် getInstance() ကို မသုံးပဲ constractor က သုံးလို့ရနေတယ်။

ConnectionPool mypool = new ConnectionPool();

ဆိုပြီး တစ်ခြား class က ခေါ်လို့ရနေပါတယ်။

ဒါကြောင့် code ကို ဒီလို ပြင်ပါမယ်။

private ConnectionPool() {
    if (pool != null) {
        throw new RuntimeException("Use getInstance() method");
    }
}

pool က null မဟုတ်တော့ဘူး။ တနည်းပြောရင် getInstance() ကို တစ်ခါ ပြောပြီးရင် instance ကို အသစ်ဆောက် ခွင့် မပြုတော့ပဲ RuntimeException ပြန်လုပ်တာပါ။

ဒါပေမယ့် Singleton က Thread safe မဖြစ်ပါဘူး။​


    Thread t1 = new Thread(new Runnable() { 
        @Override
        public void run() {
            ConnectionPool instance1 = ConnectionPool.getPool();
            System.out.println("Instance 1 hash:" + instance1.hashCode()); }
        });


        Thread t2 = new Thread(new Runnable() { 
            @Override
            public void run() {
                ConnectionPool instance2 = ConnectionPool.getPool();
                System.out.println("Instance 2 hash:" + instance2.hashCode()); }
       });

ဒီမှာ ဆိုရင် hashcode မတူတာကို တွေ့နိုင်ပါတယ်။

Singleton သဘော တရား အရ instance တစ်ခု တည်း ဖြစ်ရမှာ ဖြစ်သည့် အတွက်ကြောင့် thread safe မဖြစ်တာပါ။

Java မှာ တော့ thread safe ဖြစ်ချင်သည့် အခါမှာ function မှာ synchronized ထည့် လိုက်ရုံပါပဲ။

public static synchronized ConnectionPool getInstance() { 
        if (pool == null) {
            pool = new ConnectionPool();
        }
        return pool;
   }

နောက် တနည်းကတော့ create လုပ်သည့် အချိန်မှာ synchronized ထည့်တာပါ။

public static ConnectionPool getInstance() { 
        if (pool == null) {
            synchronized (ConnectionPool.class) { 
                if (pool == null) {
                    pool = new ConnectionPool();
                }
        }
        return pool;
}

အကယ်၍ class ကို serialize လုပ်မယ် ဆိုရင် ပြန် ပြီး unserialize လုပ်သည့် အခါမှာ instance အသစ် ထပ်ဖြစ်သွား နိုင်ပါတယ်။ အဲဒါကို ကာကွယ်ဖို့ readResolve() ကို အသုံးပြုနိုင်ပါတယ်။

public class ConnectionPool {
    private static ConnectionPool pool;
    private Connection connection = new Connection();

    private ConnectionPool() {}
    
    public static ConnectionPool getInstance() { 
        if (pool == null) {
            synchronized (ConnectionPool.class) { 
                if (pool == null) {
                    pool = new ConnectionPool();
                }
        }
        return pool;
   }
    
    public Connection getConnection(){
        return connection; 
    }
    
    protected Object readResolve() { 
        return getInstance();
    }
}