int a;
int inc(int n)
{
a += n;
return a;
}
위 코드는 A thread가 실행 도중에 B thread가 schedule된다면 엉뚱한 값을 return하게 된다. 따라서 전역변수를 제거하거나, 아래와 같이 mutex를 이용해야한다.
int a;
pthread_mutex_t a_lock = PTHREAD_MUTEX_INITIALIZER;
int inc(int n)
{
pthread_mutex_lock(&a_lock);
/* critical section */
a += n;
pthread_mutex_unlock(&a_lock);
}

public class Singleton {
// Eager Initialization
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
static이기에 class loader가 초기화하는 시점에서 정적 바인딩을 통해 해당 instance를 메모리에 등록하는 방식이다. 컴파일 시점에 object를 생성하므로 추후 실행될 일이 없으므로 thread-safe하다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
// Lazy Initailization
public static synchronzied Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
synchronized를 통해 다른 thread들의 접근을 막을 수 있으므로 instance 생성후, 더는 실행되지 않는다. 덕분에 instance가 필요한 시점에 들어온 요청에 의하여 생성되는 동적 바인딩으로 객체를 생성할 수 있다.
그러나 이는 무조건 instance가 생성됐는지, 아닌지를 확인하는 동기화 block을 거쳐야하므로 성능이 떨어진다.(synchronied 키워드를 사용하면 성능이 약 100배 떨어진다한다..!)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
// Lazy Initialization. DCL
public static Singleton getInstance() {
if(uniqueInstance == null) {
synchronized(Singleton.class) {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
1-2의 단점을 보완하여 instance가 생성되지 않았을때만 동기화 블럭을 실행한다.
multi-thread 환경에서는 기본적으로 변수 값을 읽어올때, CPU cache에 저장하여 이를 읽어오나, thread마다 읽어오는 cache가 다르므로 변수값 불일치가 발생한다.
따라서 volatile로 instance를 정의하게되면, cache가 아닌 main memory에 저장 후 읽어오므로 불일치가 발생하지 않는다. 물론 cache가 아닌 main memory에서 읽어야하므로 성능저하가 있긴 하겠지만, 1-2의 동기화 블럭을 모든 thread들이 실행하는 것보다는 성능상 유리하다.
public enum Singleton {
INSTANCE;
}
기본적으로 Enum instance의 생성은 Thread-safe하다. 따라서 코드가 간결해진다. 그러나 enum class 내의 다른 method들이 thread-safe한지는 개발자가 책임져야한다.
그러나 만들려는 Singleton이 Enum 외의 class를 상속한다면 사용할 수 없는 기법이다.
public class Singleton {
private Singleton() {}
/**
* static member class
* 내부클래스에서 static변수를 선언해야하는 경우 static 내부 클래스를 선언해야만 한다.
* static 멤버, 특히 static 메서드에서 사용될 목적으로 선언
*/
private static class InnerInstanceClazz {
// 클래스 로딩 시점에서 생성
private static final Singleton uniqueInstance = new Singleton();
}
public static Singleton getInstance() {
return InnerInstanceClazz.uniqueInstance;
}
}
1-1에서 살펴봤듯이 static 변수로 생성하면 정적 바인딩을 통해 다시 생성될일이 없으므로 깔끔하게 thread-safe가 가능하다. 그러나 lazy loading을 지원하기위해 생성 함수를 동적 바인딩으로 만드는 것이 문제였다.
위 코드에서는 생성함수의 호출이 class가 필요한 시점에 static class를 호출하며 이뤄지기에 lazy loading을 지원한다. 또한 static 변수를 final을 통해 재할당하지 않음으로써 Singleton을 보장할 수 있다.
위 방법의 경우 volatile이나 synchronized 없이 concurrency를 해결할 수 있어 성능이 뛰어나다.
원래라면 한 application에서 Singleton Object는 global하게 유일해야하지만, Spring에서는 약간 완화하여 Spring IoC Container당 하나의 Singleton Object 갖도록 제한한다. 즉, 같인 class의 object가 container가 여러개라면, Singleton Object도 여러개 존재할 수 있다.
Spring에서는 모든 bean을 singleton으로 생성하는것이 default이다.

단일 Application Context내의 두 Controller에는 동일한 Bean을 주입할 수 있다.
@RestController
public class LibraryController {
@Autowired
private BookRepository repository;
@GetMapping("/count")
public Long findCount() {
System.out.println(repository);
return repository.count();
}
}
@RestController
public class BookController {
@Autowired
private BookRepository repository;
@GetMapping("/book/{id}")
public Book findById(@PathVariable long id) {
System.out.println(repository);
return repository.findById(id).get();
}
}
위와 같이 2개의 controller는 Context내의 동일한 Bean을 주입받기에 아래와 같이 동일한 repository object임을 확인할 수 있다.
com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f
com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f

class Character:
def __init__(self, name):
self.name = name
class Warrior(Character):
def __init__(self, name):
super().__init__(name)
# Warrior-specific initialization
class Mage(Character):
def __init__(self, name):
super().__init__(name)
# Mage-specific initialization
class CharacterFactory:
def create_character(self, character_type, name):
if character_type == "Warrior":
return Warrior(name)
elif character_type == "Mage":
return Mage(name)
else:
raise ValueError("Invalid character type")
# 클라이언트 코드
factory = CharacterFactory()
character1 = factory.create_character("Warrior", "John")
character2 = factory.create_character("Mage", "Emma")
Spring에서 Factory 패턴은 Dependency Injection(DI)를 위한 Bean을 생성할때 이용된다.
Spring은 아래의 코드와 같이 BeanFactory 추상체의 getBean method를 통해 제공된 기준(name, requiredType, ...)이 일치하는 Bean을 반환한다. getBean method는 내부적으로 코드를 보면 Bean을 가져오는게 아닌, 생성해 반환하는 함수이다.
public interface BeanFactory {
getBean(Class<T> requiredType);
getBean(Class<T> requiredType, Object... args);
getBean(String name);
// ...
}
그럼 이 BeanFactory의 구현체는 누구일까?
먼저 Application 설정을 다루는 ApplicationContext Interface가 BeanFactory를 상속받는다. 왜냐면 Spring의 XML 이나 Java Annotation과 같은 외부설정을 반영한 Bean을 생성하기 위함이다.
이후 Bean Container를 시작하기 위해 ApplicationContext를 사용해야하는데, 이의 구현체가 AnnotationConfigApplicationContext이며, 상속받았던 BeanFactory의 Factory method(getBean)를 통해 Bean을 생성할 수 있다.

위에서 언급했듯 Spring이 실행되려면 Application의 설정을 담당하는 ApplicationContext 또한 생성되어야한다. 이때 다른 외부 설정파일에 맞게 ApplicationContext를 생성하는것 또한 Factory 패턴이라 볼 수 있다.
다음과 같이 AnnotationConfigApplicationContext를 ClassPathXmlApplicationContext로 변경할 수 있다.
@Test
public void givenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean() {
String expectedName = "Some name";
ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
// Same test as before ...
}
public interface Movable {
public void move();
}
public class Train implements Movable{
public void move(){
System.out.println("선로를 통해 이동");
}
}
public class Bus implements Movable{
public void move(){
System.out.println("도로를 통해 이동");
}
}
public class Client {
public static void main(String args[]){
Movable train = new Train();
Movable bus = new Bus();
train.move();
bus.move();
}
}
위 코드에서 Bus의 move method를 수정해야한다 하자. 이때 Bus의 move 함수 자체를 수정한다면, 기존의 코드를 수정하지않고 행위가 수정되어야 하는 OCP 원칙을 위배하게 된다. 또한 추후 Bus외의 다른 교통수단들이 추가되다가 모두 공통적으로 구현된 move 함수들을 수정해야한다면, 이들을 일일이 수정해야할 뿐더러 method의 중복이 발생하게 된다.
이를 해결하기위해 Strategy 패턴에서는 method의 목적에 따라 전략 class를 capsulation하여 method를 구현한다. 먼저 필요한 메서드를 목적에 맞게 class로 구현한다.
public interface MovableStrategy {
public void move();
}
public class RailLoadStrategy implements MovableStrategy{
public void move(){
System.out.println("선로를 통해 이동");
}
}
public class LoadStrategy implements MovableStrategy{
public void move() {
System.out.println("도로를 통해 이동");
}
}
그후 다음과 같이 이동에 관한 method를 object마다 직접 구현하지 않고 전략을 구현하여 이를 object마다 set 해주는 방식으로 구현한다.
public class Moving {
private MovableStrategy movableStrategy;
public void move(){
movableStrategy.move();
}
public void setMovableStrategy(MovableStrategy movableStrategy){
this.movableStrategy = movableStrategy;
}
}
public class Bus extends Moving{
}
public class Train extends Moving{
}
그 결과, client에서는 다음과 같이 move()를 기존 object의 method를 수정하지 않고 전략을 수정하는 방식으로 구현할 수 있다.
public class Client {
public static void main(String args[]){
Moving train = new Train();
Moving bus = new Bus();
/*
기존의 기차와 버스의 이동 방식
1) 기차 - 선로
2) 버스 - 도로
*/
train.setMovableStrategy(new RailLoadStrategy());
bus.setMovableStrategy(new LoadStrategy());
train.move();
bus.move();
/*
선로를 따라 움직이는 버스가 개발
*/
bus.setMovableStrategy(new RailLoadStrategy());
bus.move();
}
}
https://velog.io/@dongvelop/Java-클래스-객체-인스턴스의-차이
https://developercc.tistory.com/17
https://codedragon.tistory.com/8988
https://4z7l.github.io/2020/12/25/design_pattern_GoF.html
https://loginfo.tistory.com/2
https://www.baeldung.com/spring-framework-design-patterns
https://devmoony.tistory.com/43
https://jsonobject.tistory.com/131
https://medium.com/webeveloper/싱글턴-패턴-singleton-pattern-db75ed29c36
https://victorydntmd.tistory.com/292