전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴

     ==> 클래스의 인스턴스를 하나만 생성하여 사용하는 패턴(Ex DB Connection pool, 시스템 환경설정 등) 

            주의할점은 여러곳에서 동시에 접근해서 생길수 있는 문제(동기화문제)를 잘 파악하고 설계해야됨

     

 

1) 생성자 기본 패턴 예제 

      생성자를 private로 해서 외부에서 직접 못사용하게,  사용자에게 getInstance를 통해 생성되거나 얻을수 있게 함

package com.patterns.creationalFactory;

public class SingletonBasic {
	//싱글톤 객체를 static 변수로 선언
    private static SingletonBasic instance;
    private int msg;
    
    //외부에서 생성자 호출 막기
    private SingletonBasic(int msg) {
        this.msg = msg;
    }

    //인스턴스를 전달
    public static SingletonBasic getInstance(int msg) {
        if (instance == null) {
            instance = new SingletonBasic(msg);
        }
        return instance;
    }

    public void printMsg() {
        System.out.println(msg);
    }


//class Main {
    public static void main(String[] args) {
        SingletonBasic instance  = SingletonBasic.getInstance(1);
        SingletonBasic instance2 = SingletonBasic.getInstance(2);
        instance.printMsg();
        instance2.printMsg();
    }
}

※ Thread(동시성)문제 발생

package com.patterns.creationalFactory;

public class SingletonTreadProblem {
	private static SingletonTreadProblem instance;
    private int msg;

    private SingletonTreadProblem(int msg) {
        try {
            Thread.sleep(100);
            this.msg = msg;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static SingletonTreadProblem getInstance(int msg) {
        if(instance == null) {
            instance = new SingletonTreadProblem(msg);
        }
        return instance;
    }

    public int getMsg() {
        return msg;
    }
}

class MainTreadProblem {
    public static int num = 1;
    public static void main(String[] args) {
        Runnable run = () -> {
            num++;
            SingletonTreadProblem singleton = SingletonTreadProblem.getInstance(num);
            System.out.println("instance : " + singleton.getMsg());
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(run);
            thread.start();
        }
    }
}

처리결과

 

2) 동시성 보완  : Eager initailization(이른 초기화, Thread safe)

     클래스 로더가 생성되는 시점에 메모리에 등록 즉 변수 선언과 동시에 초기화

package com.patterns.creationalFactory;

public class SingletonThreadSafe {
	//선언과 동시에 초기화
    private static SingletonThreadSafe instance = new SingletonThreadSafe(0);
    private int msg;

    private SingletonThreadSafe(int msg) {
        try {
            Thread.sleep(100);
            this.msg = msg;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static SingletonThreadSafe getInstance() {
        return instance;
    }
    public int getMsg() {
        return msg;
    }
}
class MainThreadSafe {
    public static int num = 1;
    public static void main(String[] args) {
        Runnable run = () -> {
            num++;
            SingletonThreadSafe singleton = SingletonThreadSafe.getInstance();
            System.out.println("instance : " + singleton.getMsg());
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(run);
            thread.start();
        }
    }
}

 

3) 게으른 초기화1 : Lazy Initialization with synchronized(게으른 초기화, 동기화 블럭)

    동기화 블럭사용 : 근데 대기시간이 길다..

package com.patterns.creationalFactory;

public class SingletonSynchronized {
	private static SingletonSynchronized instance;
    private int msg;

    private SingletonSynchronized(int msg) {
        try {
            Thread.sleep(100);
            this.msg = msg;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //synchronized 키워드 사용
    public static synchronized SingletonSynchronized getInstance(int msg) {
        if(instance == null) {
            instance = new SingletonSynchronized(msg);
        }
        return instance;
    }

    public int getMsg() {
        return msg;
    }
}

class MainSynchronized {
    public static int num = 1;
    public static void main(String[] args) {
        Runnable run = () -> {
            num++;
            SingletonSynchronized singleton = SingletonSynchronized.getInstance(num);
            System.out.println("instance : " + singleton.getMsg());
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(run);
            thread.start();
        }
    }
}

 

4) 게으른 초기화2 :  Lazy Initialization. Double Checking Locking(DCL)

      java에서 가장 늦은연산자는 new연산자이다 그러므로 생성시점이 아니라 메서드 내 호출시로 내리자 

package com.patterns.creationalFactory;

public class SingletonDCL {
	
	//Lazy Initialization. Double Checking Locking(DCL)
	
	private static SingletonDCL instance;
    private int msg;

    private SingletonDCL(int msg) {
        try {
            Thread.sleep(100);
            this.msg = msg;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static SingletonDCL getInstance(int msg) {
        if (instance == null) {
            //instance가 null인 경우 synchronized 블록 접근
            synchronized (SingletonDCL.class) {
                if (instance == null) {
                    instance = new SingletonDCL(msg);
                }
            }
        }
        return instance;
    }

    public int getMsg() {
        return msg;
    }
}

class MainDCL {
    public static int num = 1;

    public static void main(String[] args) {
        Runnable run = () -> {
            num++;
            SingletonDCL singleton = SingletonDCL.getInstance(num);
            System.out.println("instance : " + singleton.getMsg());
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(run);
            thread.start();
        }
    }
}

 

5) 게으른 초기화3 :  Lazy Initialization. LazyHolder

이 방법은 클래스안에 클래스를 두는 holder방법을 이용한 것이다.
아래 코드에서 중첩 클래스의 instance는 getInstance()가 호출되기 전까지는 초기화 되지않는다. 또한 instance는 static이므로 클래스 로딩 시점에 한번만 호출되고 final을 사용해 다시 값이 할당되지 않도록 함으로써 동시성 문제를 해결할 수 있다.

가장 성능이 좋고 많이 쓰이는 방식이다.

package com.patterns.creationalFactory;

public class SingletonLazyHolder {
	
	//Lazy Initialization. LazyHolder
	private static SingletonLazyHolder instance = new SingletonLazyHolder(0);
    private int msg;

    private SingletonLazyHolder(int msg) {
        try {
            Thread.sleep(100);
            this.msg = msg;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //static 클래스안에 static 멤버 변수 선언 및 초기화
    private static class Initial {
        private static final SingletonLazyHolder instance = new SingletonLazyHolder(0);
    }
    public static SingletonLazyHolder getInstance() {
        return Initial.instance;
    }

    public int getMsg() {
        return msg;
    }
}

class MainLazyHolder {
    public static int num = 1;
    public static void main(String[] args) {
        Runnable run = () -> {
            num++;
            SingletonLazyHolder singleton = SingletonLazyHolder.getInstance();
            System.out.println("instance : " + singleton.getMsg());
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(run);
            thread.start();
        }
    }
}

 

 

참고자료 : https://cjw-awdsd.tistory.com/42

 

[디자인 패턴] Singleton Pattern 개념/예제

1. 싱글톤 패턴 싱글톤 패턴이란 클래스의 인스턴스를 하나만 생성하여 사용하는 패턴이다. 주로 특정 객체를 여러곳에서 공유해야 할 때 사용한다.(Ex: DB Conntection pool) 싱글톤 패턴을 이용함으

cjw-awdsd.tistory.com

                https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS8616098823 

 

[Design pattern] 많이 쓰는 14가지 핵심 GoF 디자인 패턴의 종류

디자인 패턴을 활용하면 단지 코드만 ‘재사용’하는 것이 아니라, 더 큰 그림을 그리기 위한 디자인도 재사용할 수 있습니다. 우리가 일상적으로 접하는 문제 중 상당수는 다른 많은 이들이 접

www.hanbit.co.kr

 

+ Recent posts