보글보글 개발일지
반응형

면접 질문으로 받았는데... 제대로 대답하지 못했다.

싱글톤 패턴이란?

생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나. 최초 생성 이후 호출된 생성자는 최초의 생성자가 생성한 객체 리턴. 

싱글톤 패턴은 단 하나의 인스턴스를 생성해 사용하는 디자인 패턴

사용 이유?

1. 메모리 측면
싱글톤 패턴을 사용하게 된다면 한개의 인스턴스만을 고정 메모리 영역에 생성하고 추후 해당 객체를 접근할 때 메모리 낭비를 방지할 수 있다. -> 고정된 메모리 영역, 한번의 new로 인스턴스 사용

2. 속도 측면
생성된 인스턴스를 사용할 때는 이미 생성된 인스턴스를 활용하여 속도 측면에 이점이 있다. 객체 로딩 시간 감소

3. 데이터 공유
전역으로 사용하는 인스턴스이기 때문에 다른 여러 클래스에서 데이터를 공유하며 사용할 수 있다. 하지만 동시성 문제가 발생할 수 있어 이 점은 유의하여 설계하여야 한다.

언제 사용?

공통된 객체를 여러개 생성해서 사용하는 DBCP(DataBase Connection Pool)와 같은 상황에서 많이 사용.

(쓰레드풀, 캐시, 대화상자, 사용자 설정, 레지스트리 설정, 로그 기록 객체등)

문제점?

  • 싱글톤 인스턴스가 너무 많은 일 하거나 데이터 공유할 시, 다른 클래스 인스턴스 간 결합도 증가 -> 개방-폐쇄 원칙 위배! -> 수정 및 테스트 어려움 발생.
  • 멀티 쓰레드 환경에서 동기화 처리 하지 않으면 인스턴스 두개 생성되는 문제 발생 가능, 변수 값의 일관성 유지 불가

구현방식

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;
    }
}

멀티 쓰레드 환경에서 발생하는 문제 해결

  1. 정적 변수 선언에서 인스턴스 생성
  2. synchronized 사용 -> 멀티 스레드 환경에서의 동시성 문제 해결. 그러나 thread-safe 보장 위해 성능 저하 발생
  3. 위 방식 해결하기 위해 Double - checked locking 추가 (성능 완화 정도. 완벽하지 않음) - 
    1. 첫 if문으로 인스턴스 존재여부 체크
    2. 두번째 if문에서 다시 한 번 체크할 때 동기화 시켜서 인스턴스 생성 -> thread-safe, 처음 생성 이후에 synchronized 써서 성능 저하 완화
  4. Initialization on demand holder idiom (holder에 의한 초기화) -> 가장 많이 사용. 일반적.
    클래스안에 클래스(Holder)를 두어 JVM의 Class loader 매커니즘과 Class가 로드되는 시점을 이용한 방법.
    JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용해 싱글톤 초기화 문제를 JVM에게 책임 전가
    holder안에 선언된 instance가 static이기 때문에 클래스 로딩시점에 한번만 호출될 것이며 final을 사용해 다시 값이 할당되지 않도록 만든 방법.

//1. 정적 변수선언에서 인스턴스 생성
//static 변수로 싱글톤 인스턴스 생성.
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

//2. synchronized 사용 -> 성능 저하 발생
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;
        }
    }
}
//3. Double - checked locking 추가 (성능 완화 정도. 완벽하지 않음)

public class Singleton {
    public class Singleton {
        private static Singleton instance;

        private Singleton() {}
        
        public static Singleton getInstance() {
        	if(instance == null){
            	synchronzied (Singleton.class){
            	if(instance  == null) 
                	instance  = new Singleton();
            	}
            }
          
            return instance;
        }
    }
}

//4.Initialization on demand holder idiom (holder에 의한 초기화)
public class Singleton {
    public class Singleton {
        private Singleton() {}
        
        private static class LazyHolder{
        	public static final Singleton instance = new Singleton();
        }
        
        public static Singleton getInstance() {
            return LazyHolder.instance;
        }
    }
}

그래서 언제쓰지?

메모리, 속도, 데이터 공유 측면에서 이점이 있다. 그러나 멀티 스레드 환경에서는 동시성 문제 발생 가능성이 있기 때문에 반드시 객체의 인스턴스가 한 개만 존재하는지?, 동시성 문제 발생하지 않는지?를 고려해야함.

 

출처

 

반응형
profile

보글보글 개발일지

@보글

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!