JaeWon's Devlog
article thumbnail
반응형

이번 글에서는 디자인패턴의 한 종류인 싱글톤(Singleton) 패턴에 대해서 알아보고자 한다.

다양한 디자인패턴이 있지만, 자주 사용되는 싱글톤패턴을 조금 더 이해하고, 사용하고자 하여 정리해보려 한다.


1. 싱글톤(Singleton) 패턴이란?

- 정의는 간단하게, 객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미한다.

- 어플리케이션이 시작할 때, 어떤 클래스에서 최초 1번만 메모리를 할당하고(static) 그 메모리에 인스턴스를 만들어 필요할 때 사용하는 패턴이다.

- 위의 그림을 예로 들어보면, 회사에 공용으로 사용하는 1대의 프린트가 있을 때 회사원들은 개인이 필요할 때마다 해당 프린트를 사용한다.

 

2. 싱글톤패턴 장,단점

2-1. 장점

- 최초 한번의 인스턴스 생성으로 고정된 메모리 영역을 사용하게 되어, 추후 동일 객체를 생성(접근)하게 될 때 메모리 낭비를 방지 할 수 있다.

- 싱글톤 인스턴스가 전역으로 사용됨으로서, 다른 클래스에서 해당 인스턴스에 접근하여 데이터를 공유하기가 쉽다.

- 두 번째로 이용시에는 객체 로딩 시간이 줄어 성능이 좋아진다.

2-2. 단점

- 어플리케이션의 성능을 고려해 구현이 필요하다. 멀티스레딩 환경에서 발생할 수 있는 동시성 문제와 같은 것을 고려해 구현해야 한다.

- 테스트가 어렵다. 싱글톤 인스턴스는 자원을 공유하기 때문에 테스트가 결정적으로 격리된 환경에서는 매번 인스턴스의 상태를 초기화해야 한다.

- 의존 관계상 클라이언트가 구체 클래스에 의존하게 된다. 이는 다른 클래스의 인스턴스들 간에 결합도가 높아져 "개방-폐쇄 원칙"을 위배하게 된다.

 

3. 싱글톤 패턴 구현

  • 기본적인 구현
public class singleTon {
    
	private String str;
	
	//Instance
    	private static singleTon instance = new singleTon();

    	//private construct
    	private singleTon() {
    	    str = "싱글톤 인스턴스";
    	}

    	public static singleTon getInstance() {
    	    return instance;
    	}

	public String getStr() {
	    return str;
	}

	public void setStr(String str) {
	    this.str = str;
	}
}
public class singleTonMain {
	public static void main(String[] args) {
		singleTon test1 = singleTon.getInstance();
		singleTon test2 = singleTon.getInstance();
		singleTon test3 = singleTon.getInstance();
		
		System.out.println(test1.getStr());
		System.out.println(test2.getStr());
		System.out.println(test3.getStr());
		
		test1.setStr("싱글톤 데이터 변경");
		
		System.out.println(test1.getStr());
		System.out.println(test2.getStr());
		System.out.println(test3.getStr());
	}
}
싱글톤 인스턴스
싱글톤 인스턴스
싱글톤 인스턴스
싱글톤 데이터 변경
싱글톤 데이터 변경
싱글톤 데이터 변경

 

- instance 라는 전역 변수를 선언하는데, static 을 할당함으로써 인스턴스화 하지 않고 사용할 수 있게 하였지만 접근 제한자를 private로 하여 직접적인 접근을 불가능하게 하였다.

- 생성자도 private으로 되어 있어 new 키워드를 통해 객체 생성도 불가능하다.

- 일반 클래스의 경우에는 클래스를 정의할 때 new 키워드를 사용하여 생성하지만, 싱글톤 클래스의 경우 getInstance() 메소드를 사용한다.

- 위의 예제 main 클래스 코드에서 test1 인스턴스에 데이터를 변경하였는데, test2,test3도 같이 변경된 것을 확인할 수 있고, 같은 인스턴스를  사용한다고 알 수 있다.

싱글톤 객체를 사용할 때는, 데이터가 변경되는 것을 지양한다.(예를 들기 위해서 사용)
위에서 보았듯이, 데이터를 변경하면 다른 곳에서도 생성한 인스턴스 데이터도 바뀌기 때문이다.

 

  • static block
public class singleTon {
    //Instance
    private static singleTon instance;

    //private construct
    private singleTon() {
    }
    
    static {
        try {
            instance = new singleTon();
        } catch(Exception e) {
            throw new RuntimeException("Create Fail SingleTon Instance");
        }
    }

    public static singleTon getInstance() {
        return instance;
    }
}

- static block 형태로 사용할 경우 클래스가 로딩될 때 한번 실행을 하게 되는 특성을 사용한다.

- 인스턴스가 사용되는 시점이 아닌, 클래스 로딩 시점에 실행된다.

 

  • lazy init
public class singleTon {
    //Instance
    private static singleTon instance;

    //private construct
    private singleTon() {
    }

    public static singleTon getInstance() {
        if(instance == null) {
            instance = new singleTon();
        }
        return instance;
    }
}

- static block 방법을 개선하여 클래스 로싱 시점이 아닌 인스턴스 호출할 때 생성되는 형태로 구현하였다.

- 그렇지만, 위 형태에선 멀티 쓰레드 환경(multi-thread)에서는 취약하다.

- 특정 쓰레드에서 동시에 getInstance() 메소드를 호출 시 인스턴스가 두 번 생성되는 문제가 발생할 수 있다.

 

  • Thread Safe + lazy
public class singleTon {
    //Instance
    private static singleTon instance;

    //private construct
    private singleTon() {
    }

    public static synchronized singleTon getInstance() {
        if(instance == null) {
            instance = new singleTon();
        }
        return instance;
    }
}

- 멀티쓰레드환경의 문제점을 synchronized 키워드를 붙임으로써 쓰레드에서 동시 접근에 대한 문제를 해결한다.

- 그렇지만, 해당 키워드는 쓰레드 동기화 문제로 어플리케이션의 성능 저하를 발생시킨다.

 

  • Holder
public class singleTon {
    //private construct
    private singleTon() {
    }
    
    private static class InnerInstanceClass {
    	private static final singleTon instance = new singleTon();
    }

    public static singleTon getInstance() {
    	return InnerInstanceClass.instance;
    }
}

- JVM의 클래스 로더 메커니즘과 클래스의 로드 시점을 이용한 내부 클래스를 통해 생성 시킴으로써 멀티쓰레드 간의 동기화 문제를 해결한다.

- Java에서 싱글톤 생성 시 사용되는 대표적인 방법이다.


참고

- https://elfinlas.github.io/2019/09/23/java-singleton/

- https://yeolco.tistory.com/122

반응형

'디자인패턴' 카테고리의 다른 글

[디자인패턴] 전략(Strategy) 패턴  (0) 2023.04.21
[디자인패턴] 생성패턴 - Builder 패턴  (0) 2022.07.09
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!