오늘은 디자인 패턴 중 생성 패턴의 싱글톤 패턴에 대해 알아보자!
싱글톤(Singleton) 패턴이란?
싱글톤 패턴은 인스턴스를 오직 1개만 생성하고 이를 리턴해주는 패턴이다.
이러한 패턴은 주로 프로그램 내에서 하나로 공유를 해야 하는 객체가 존재할 때 해당 객체를 싱글톤으로 구현하여 모든 유저 또는 프로그램들이 해당 객체를 공유하며 사용하도록 할 때 사용된다.
즉, 싱글톤 패턴은 아래와 같은 상황에서 사용한다.
- 프로그램 내에서 하나의 객체만 존재해야 한다.
- 프로그램 내에서 여러 부분에 해당 객체를 공유하여 사용해야한다.
싱글톤 패턴을 사용하는 이유
하나의 인스턴스만을 사용하는 싱글톤 패턴의 이점은 다음과 같다.
1. 메모리 측면의 이점 : 싱글톤 패턴을 사용하게 된다면 한 개의 인스턴스만을 고정 메모리 영역에 생성하고 추후 해당 객체를 접근할 때 메모리 낭비를 방지할 수 있다.
2. 속도 측면의 이점 : 생성된 인스턴스를 사용할 때는 이미 생성된 인스턴스를 활용하여 속도 측면에 이점이 있다.
3. 데이터 공유의 편의 : 전역으로 사용하는 인스턴스이기 때문에 다른 여러 클래스에서 데이터를 공유하며 사용할 수 있다. 하지만 동시성 문제가 발생할 수 있어 이 점은 유의해야 한다.
싱글톤 패턴 구현하기
public class Singleton {
// 단 1개만 존재해야 하는 객체의 인스턴스로 static 으로 선언
private static Singleton instance;
// private 생성자로 외부에서 객체 생성을 막아야 한다.
private Singleton() {
}
// 외부에서는 getInstance() 로 instance 를 반환
public static Singleton getInstance() {
// instance 가 null 일 때만 생성
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
싱글톤 패턴의 기본 구현 방법은 다음과 같다.
먼저 private static으로 Singleton 객체의 Instance를 선언하고 getInstance() 메서드가 처음 실행될 때만 하나의 instance가 생성되고 그 후에는 이미 생성된 instance를 return 하는 방식으로 진행된다.
여기서 핵심은 private로 된 기본 생성자이다. 생성자를 private로 생성하며 외부에서 새로운 객체의 생성을 막아줘야 한다.
// 같은 instance인지 Test
public class Application {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}
}
/** 출력
* vendingmachine.Singleton@15db9742
* vendingmachine.Singleton@15db9742
**/
싱글톤 객체를 생성하는 위 코드를 실행해 보면 두 객체가 하나의 인스턴스를 사용하여 같은 주소 값을 출력하는 것을 확인할 수 있다.
Multi-thread에서의 싱글톤
Multi-thread환경에서 싱글톤을 사용하게 된다면 다음과 같은 문제가 발생할 수 있다.
1. 여러 개의 인스턴스 생성
Multi-thread환경에서 instance가 없을 때 동시에 아래의 getInstance() 메서드를 실행하는 경우 각각 새로운 instance를 생성할 수 있다.
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
2. 변수 값의 일관성 실패
다음과 같은 코드가 실행되었을 때, 여러 개의 thread에서 plusCount()를 동시에 실행한다면 일관되지 않은 값들이 생길 수 있다.
public class Singleton {
private static Singleton instance;
private static int count = 0;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public static void plusCount() {
count++;
}
}
이제 해결법에 대해 알아보자!
1. 정적 변수 선언에서 인스턴스 생성
이러한 문제는 static 변수로 singleton 인스턴스를 생성하는 방법으로 해결할 수 있다.
아래와 같이 초기에 인스턴스를 생성하게 된다면 Multi-thread 환경에서도 다른 객체들은 getInstance를 통해 하나의 인스턴스를 공유할 수 있다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
2. synchronzied의 사용
synchronized를 적용하여 Multi-thread환경에서의 동시성 문제를 해결하는 방법이다.
하지만 이 방법은 Thread-safe를 보장하기 위해 성능 저하가 발생할 수 있다.
(여기서 Thread-safe란 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없는 것을 말한다.)
public class Singleton {
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronzied Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
}
싱글톤 패턴은 메모리, 속도, 데이터 공유 측면에서 이점을 가지고 있다. 다만 그렇다고 해서 싱글톤 패턴이 무조건 좋은 것은 결코 아니다.
Multi-thread환경에서는 동시성 문제가 발생할 수 있기에 싱글톤 패턴을 사용하고자 한다면 사용하기 앞서 "해당 객체의 인스턴스가 한 개만 존재하여야 하는지?" 여부와 "사용을 하였을 때 동시성 문제가 발생하지 않는지"를 생각하고 확인하며 사용해야 한다!
참고
'📒 기술 또는 개념 > 🎨 Design Pattern' 카테고리의 다른 글
[Design Pattern] 어댑터(Adapter) 패턴에 대해 (0) | 2023.10.30 |
---|---|
[Design Pattern] 팩토리(Factory) 패턴에 대해 (0) | 2023.10.25 |
[Design Pattern] 빌더(Builder) 패턴에 대해 (0) | 2023.10.24 |