목차
- 정의
- 사용 배경
- 싱글톤 코드 예제
- 싱글톤 코드 특징
- 싱글톤 코드 단점
- 스프링에서의 싱글톤 지원
1. 정의
-말그대로 하나의 객체만을 생성해 이후 호출되는 곳에서는 생성된 객체를 반환하여 프로그램 전반에서 하나의 객체만 사용하게 하는 패턴
2. 사용 배경
-단순한 일을 하는 어떤 클래스가 있다고 가정해봅시다. 사용자는 이를 사용하기위해 객체를 만들 것입니다. 하지만 사용자가 만명이라면?? 만 개의 객체가 메모리에 할당되어야합니다. 이런식으로의 메모리 사용 낭비를 막기위해 싱글톤 패턴을 사용합니다. static 으로 메모리에 한번 올려놓고 하나를 돌려쓰는 것입니다.
3. 싱글톤 코드 예제
package hello.core.singleton;
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
4. 싱글톤 코드 특징
1. 생성자를 private으로 하여 다른 클래스에서 객체를 생성하지 못하게 막음
private SingletonService() {
}
2. method 를 public 으로 하여 미리 생성해 둔 객체를 외부에서 참조할 수 있게함.
public static SingletonService getInstance() {
return instance;
}
Q) 메소드에 static은 왜 붙나요?
메소드 앞에 static을 붙이면 객체 생성없이 클래스를 통해 메서드를 직접 호출할 수 있습니다.
앞서 말한 것처럼 싱글톤 패턴은 외부에서 객체 생성을 할 수 없게 생성자가 private으로 되어있습니다. 만약 static이 없다면 외부에서는 객체를 생성하지 못하기 때문에 메소드 또한 사용하지 못하게 됩니다. 그러면 instance 를 return 받을 방법이 없게 됩니다. 따라서 static을 붙이게 됩니다.
5. 싱글톤 패턴의 단점
1.private 생성자를 갖고 있어 상속이 불가능하다.
-상속을 통해 다형성을 적용하기 위해서는 다른 기본 생성자가 필요하므로 객체 지향의 장점을 적용할 수 없다. 또한 싱글톤을 구현하기 위해서는 객체지향적이지 못한 static 필드와 static 메소드를 사용해야 한다.
2. 객체지향의 SOLID 원칙을 지키지 못한다.
- Singleton은 구현에 의존하고 있다.
3.MultiThread 환경에서 문제가 발생한다.
-두 개 이상의 스레드가 인스턴스를 획득하기 위해 접근하는 과정에서 값이 달라질 수 있다.
예시 코드
- 상황: 사용자1이 10000원을 주문하고, 사용자 2가 20000원을 주문 한 경우, price는 10000원이 아닌 20000원이 나온다.
이유: 같은 객체를 공유하고 있기 때문
public class StatefulService {
private int price; // 상태를 유지하는 필드
public void order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
this.price = price; // 여기가 문제
}
public int getPrice() {
return price;
}
}
class StatefulServiceTest {
@Test
void statefulServiceSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA: 사용자1 10,000원 주문
statefulService1.order("userA", 10000);
//ThreadB: 사용자2 10,000원 주문
statefulService2.order("userB", 20000);
//ThreadA: 사용자1 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = " + price);
assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
결론: 공유 필드 조심해야함. 스프링 빈은 항상 무상태(stateless) 로 설계해야 함.
해결방안: 지역변수 등 활용하기
해결 코드
public class StatefulService {
// private int price; // 상태를 유지하는 필드
public int order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
return price; // 여기가 문제
}
class StatefulServiceTest {
@Test
void statefulServiceSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA: 사용자1 주문 금액 조회
int userAPrice = statefulService1.order("userA", 10000);;
int userBPrice = statefulService2.order("userB", 20000);;
System.out.println("userAPrice = " + userAPrice);
System.out.println("userBPrice = " + userBPrice);
assertThat(userAPrice).isNotEqualTo(userBPrice);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
6. 스프링에서의 싱글톤 지원
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
// AppConfig appConfig = new AppConfig();
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//1.조회: 호출때마다 객체 생성
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
//참조값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
//memberService1 != memberService2
assertThat(memberService1).isSameAs(memberService2);
}
따로 싱글톤 관련 코드들을 짜지 않았지만, 호출을 할 때 싱글톤 방식이 적용된 객체가 튀어나오는 것을 알 수 있다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Android] 카카오 링크 보내기 실패 keyhash 등록 에러 (0) | 2022.05.05 |
---|---|
[Android java] Dialog에서 setProgressDrawable 하는 방법 (Context문제) (0) | 2022.04.27 |
[Android java] nullPointerException오류 실수원인 참고 (0) | 2022.04.19 |
[Android java] "Only the original thread that created a view hierarchy can touch its views." 해결 (0) | 2022.04.13 |
객체지향의 원칙 OCP 와 DIP에 대해 (0) | 2022.04.05 |