싱글톤 패턴은 소프트웨어 디자인 패턴중 하나이다.
주로 사용되는 경우는 단 하나만 존재해야되는 물건이 있을 때!
프로그램 내부에서 단 1개만 생성됨을 보장하며 멀티쓰레드에서 이 객체를 동시에 접근하는 경우에 발생하는 동시성 문제를 해결해준다.
말로설명하면 어렵고 코드를 보면 쉽다.
class Order {
public void start() {
A a = A.getInstance();
A b = A.getInstance();
// 확인
System.out.println(a == b); // true
}
}
만약 이렇게 있다고 치면, a 와 b 는 같은 객체를 보고있게 된다.
이런 로직을 만들 필요가 있을때 싱글톤패턴을 사용한다.
1. 기본적인 구현방법 (리소스낭비)
public class A {
private A() {
System.out.println("Hello singleton!!");
}
private static A singleton = new A(); //객체생성
public static A getInstance() {
return A;
}
}
private 생성자를 사용하여 외부에서의 생성을 막은것이 포인트다
단점으로는 클래스가 로드될때 객체가 생성되므로 리소스 낭비가 있을 수 있다.
그렇다면 어떻게 개선할수있을까?
2. 기본적인 구현방법 (리소스낭비 줄임)
public class A {
// 단 1개만 존재해야 하므로 static
private static A a;
// private로 외부생성을 막음
private A() {
System.out.println("Hello singleton!!");
}
//외부에서는 getInstance로 접근
public static A getInstance() {
if (instance == null){
instance = new A();
}
return instance;
}
}
1) getInstance()
- static을 쓰지않으면 외부에서 사용할때
new A().new getInstance()
이렇게 새로운 인스턴스를 선언하면서 써야하기때문에 싱글톤이 보장되지 않는다.
따라서 static을 사용하고 if 문을 통하여 기존에 불려진 instance가 없다면 생성, 있다면 기존 instance를 return
하지만 단점!
이 경우 Multi Thread 환경에서 Thread-safe 를 보장해주지 않는다.
n개의 사용자가 동시에 getInstance를 했을때 new A() 를 n번 생성할수도있다.
이를 막기위해서 Synchronized, Double check 등을 사용해서 막아볼수도 있겠지만, 가장많이 쓰이는 방법은 아래와 같다.
3. LazyHolder 싱글톤 패턴 ✔️
public class A {
private A() {
System.out.println("Hello singleton!!");
}
private static class LazyHolder() {
private static final A instance = new A();
}
public static A getInstance() {
return LazyHolder.instance;
}
}
훨씬 코드가 예뻐진것을 볼수있다.
해당 클래스가 로드되더라도 getnstance() 메서드가 호출될때까지 로드되지 않으므로 리소스 낭비도 줄이고
A.getInstance() 를 호출할때마다 동일한 instance가 변환된다. 왜냐! private static final 로 선언했기때문!
사용은 아래와 같이 쓰면된다
public static void main(String[] args) {
A a = A.getInstance();
}
Spring 의 경우에는 static 기반의 싱글톤 구현을 지양하고있으며 더욱 간단하게 쓸수있다.
Spring - @Autowired 사용
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class A {
public A() {
System.out.println("Hello singleton!!");
}
}
@Component
public class Main {
@Autowired
private A a; // A의 싱글톤 인스턴스가 주입됨
public void display() {
System.out.println(a);
}
}
// 메인 메서드
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Main main = context.getBean(Main.class);
main.display();
// 다시 가져와서 같은 인스턴스인지 확인
Main anotherMain = context.getBean(Main.class);
System.out.println(main.a == anotherMain.a); // true, 같은 인스턴스
}
}
위와 같이 인스턴스 필드로 의존성 주입을 사용하는 경우, 싱글톤 패턴이 유지된다.
Spring 컨테이너는 @Component, @Service, @Repository, @Controller 등으로 정의된 클래스의 인스턴스를 싱글톤으로 관리한다
항상 어노테이션을 사용하여 써왔기때문에 싱글톤 패턴을 정석으로 봤을때 생소했던것같다.