객체생성에 제한을 걸기 위한 방법이다.
ex)
public class SingleTon {
private static SingleTon instance = new SingleTon();
private SingleTon() {}
public static SingleTon getInstance() {
return instance;
}
}
클라이언트에서 사용하지 않는 경우에도 인스턴스가 생길 수 있다
위에 제공된 코드는 사실 호출될 수 있는 요소가 getInstance메서드 밖에 존재하지 않아서, getInstance메서드가 호출되지 않는한 사용되지 않는 singleTon클래스는 로드되지 않아 클래스 안에 static요소들도 초기화되지 않는다고 한다. 하지만 만약 getInstance메서드 이외에 다른 static메서드가 있고 이 메서드가 다른 곳에서 호출이 된다면 자동으로 singleTon클래스도 로드되면서 singleTon객체가 생성되게 된다. 즉, singleTon객체 생성이 필요하지 않더라도 해당 클래스의 다른 메서드를 썼다고 객체가 미리 생성되게 되는 상황이다.
이를 해결하기 위한 방법 --> Lazy Initialization
ex)
public class Singleton {
private static Singleton instance; //---> Eager Initialization과 달리 여기서 생성자를 통해 객체를 만들지 않는다.
private Singleton() {}
public static Singleton getInstance(){
if(Objects.isNull(instance)) {
instance = new Singleton();
}
return instance;
}
}
Eager Initialization와 달리 생성자를 getInstance메서드 안으로 넣어버려서 getInstance메서드가 호출되어야만 singleTon객체가 생성되게 된다.
--> SingleTon클래스의 다른 메서드나 필드가 호출되었을 때 SingleTon객체가 미리 생성되는 문제점 해결!
스레드 세이프하지 않다
이런 singleTon패턴의 문제점은 '멀티스레드'환경에서 발생한다. 멀티스레드는 간략하게 설명하면 여러 스레드를 통해 각 스레드가 각자 따로 움직이도록 만들어 하나의 프로그램이 동시에 여러일을 할 수 있게 하는 방법이다. 이런 환경에서 스레드1과 스레드2가 동시에 getInstance메서드를 호출하게 되면 동시에 instance를 생성하게 되어 유일무이하지 않은 객체 2개가 생성된다.
말 그대로 두 번 체크하는 방식이다. 체크에는 synchronized가 사용되고 데이터의 읽기,쓰기를 메인메모리만이 관리하도록 volatile을 이용한다.
public class SingleTon {
private static volatile SingleTon instance;
private SingleTon() {}
public static SingleTon getInstance() {
if (instance == null){ //-----------------> 첫번째 체크
synchronized (SingleTon.class) {
if (instance == null){ //-----------------> 두번째 체크
instance = new SingleTon();
}
}
}
return instance;
}
}
자바에 프로그램이 처음 실행될 때 class가 classloader에 의해 synchronized가 자동 실행되도록 설계되어있다. 이를 이용해 직접 synchronized 구문을 작성하지 않고 singelTon으로 만들고 싶은 클래스의 생성자 자체에 'private static class' 처리를 해주는 것이다. 마치 class로 다시 덮어주는 모습과 비슷해 holder라고도 부른다.
public class SingleTon {
private static class SingleTonHolder{ //--------> 'private static class' 클래스 구조로 감싸기
private static SingleTon instance = new SingleTon();
}
private SingleTon() {}
public static SingleTon getInstance() {
return SingleTonHolder.instance; //--------> SingleTon클래스가 아닌 SingleTonHolder클래스의 인스턴스임
}
}
문제점 : 클라이언트가 임의로 singleTon파괴가 가능한 구조다. 리플렉션 직렬화+역직렬화
리플렉션(Reflection)은 자바 API의 한 종류로 클래스 타입을 모르는 상황에 클래스 타입을 알아내거나, 메서드, 변수 등을 만들거나 수정할 수 있다. 즉, private설정을 통해 singleTon이 유지될 때 private설정을 바꿈으로 singleTon패턴이 깨질 수 있다.
직렬화(Serialization)란 객체들의 데이터를 연속적인 흐름의 형태로 만드는 것이고, 역직렬화(Deserialization)란 직렬화의 반대로 데이터의 연속적인 흐름의 형태를 다시 데이터를 가지고 있는 객체형태로 만드는 것이다. 이렇게 역직렬화하는 과정에서 객체를 만드는 부분있는데, singleTon객체라도 객체가 생성되게 된다. 하지만 이때 readResolve()메서드를 통해 역직렬화하는 과정에서 객체생성을 막고 이미 생성된 객체를 받아가게 하여 singleTon을 유지시킬 수 있다.
Enum은 lazy initializaion의 문제점인 스레드 세이프하지 않음을 해결하지만, lazy loading을 할 수 없어 lazy initialization의 장점은 살리지 못했다. 하지만 스레드 세이프함 외에도 직렬화/역직렬화에 대한 문제점을 해결하여 안전한 패턴이다.
public enum SingleTon {
instance;
}
문제점 : 싱글턴 해제 번거로움, Enum외 클래스 상속 불가
수업 때 사용한 코드를 분석해 정리해보았다.

singleTon을 사용하는 이유와 정적 메서드나 정적 필드를 사용하는 이유는 같을 것이다. 유일무이한 개체를 만들고 이를 공유하며 사용하기 위해서일 것이다. 그럼 두 방식의 차이점은 무엇일까?
내생각(틀릴 수 있음)
1. 리소스 낭비를 막고 싶을 때 singleTon패턴 lazy loading 사용 : singleTon으로는 lazy loading을 구현해 리소스 낭비를 막을 수 있지만, 정적메서드나필드는 그렇지 못하다. 그래서 정적인 메서드나 필드로 클래스를 만들어 객체를 만드는데 early loading으로 리소스가 낭비된다 싶으면 singleTon으로 lazy loading을 구현해 리소스 낭비를 막을 수 있다.
2. 정적바인딩을 통해 정적요소가 보통 효율이 좋음 : 유틸리티 클래스(인스턴스 메서드나 필드 제공안하며 정적 메서드와 필드만 제공하는 클래스)처럼 기능만을 제공하는 느낌의 클래스에는 정적 타입이 사용하기 좋다.