나의 롤모델인 친구들과 자율적으로 각자 한가지 주제를 정해 공부하고 서로에게 설명해주며 공부하는 형식의 스터디를 시작했다

내가 선택한 주제는 singleton이다. 수업시간에 배운 singleton이 정확하게 어떤 것이고 언제 사용하는지가 더 자세히 알고싶었다.


내가 알고있는 싱글톤은 인스턴스가 한번도 생성되지 않았다면 인스턴스를 생성(메모리 할당)하고 만약 인스턴스가 생성되어있다면 기존에 만들어진 인스턴스를 반환시켜 하나의 인스턴스를 계속 재사용하면서 메모리 낭비를 줄이는 방법이다.


싱글톤 디자인 패턴

디자인 패턴이란 상황에 따라 자주 쓰이는 설계방법을 정리한 방법론이다.

싱글톤 패턴의 단점

다른 객체와 공유하기 때문에 객체간의 결합도가 높아져서 객체지향 설계 원칙인 개방-폐쇄 원칙에 어긋난다.

소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.

싱글톤 객체는 수정할 경우 사이드 이팩트(의도하지 않은 결과) 발생 확률이 높아진다.
또한 다중쓰레드 환경에서 여러개의 스레드가 동시에 접근하면 여러개의 객체가 생성될 수 있는 동기화 문제가 발생할 수 있다.


여러가지 싱글톤 생성 방법

  1. private staitc 변수. private 생성자, static 블럭 혹은 변수 생성하면서 바로 인스턴스 초기화하는 방식.
    클래스가 로딩되면서 인스턴스가 바로 생성된다. 그러나 실제로 사용할지 안할지 모르는 변수를 미리 만들어놓는 문제가 생겨 메모리 낭비라고 볼 수 있음.

  2. private static 전역변수, private 생성자로 직접적인 접근 불가능하다. 오직 public static 메서드를 통해서만 인스턴스를 생성하고 반환받아 사용할수있는 다소 고전적인 방식이다.
    public static 메소드로 인스턴스가 생성되지 않았을 때만 생성하고 생성되어있으면 기존의 인스턴스를 반환하는 형식인데 이건 멀티 쓰레드환경에서 취약한 단점이 있음.

    멀티쓰레드에서 취약한 이유
    만약 두개의 쓰레드가 동시에 메서드를 호출한다면 인스턴스가 두번생성됨

public class Test(){
	private static Test instance=null;
    
    private Test();
 
    public static Test getInstance(){
    	if(instance==null){
        	instance=new Test();
        }
        return instance;
    }
}
  1. public static synchronized 메소드로 인스턴스를 생성하고 반환하는 형식.
    static synchronized 메소드는 클래스 객체를 기준으로 동기화되는데 클래스 객체는 클래스당 하나만 존재할 수 있어서 하나의 쓰레드만 static synchronized 메서드를 실행할 수 있음. 멀티쓰레드의 동시접근을 막을순 있다. 그러나 동기화는 성능저하 문제가 있다.

    synchronized 키워드 사용의 단점
    인스턴스 최초 생성시에 스레드의 동시접근으로 인한 중복 생성만 막으면 되는데 인스턴스 재사용 시에도 동기화가 사용되므로 불필요한 기능이 된다.

  1. DCL 싱글톤패턴(double checked locking)이라고 하며 인스턴스를 생성하는 블록만 synchronized 처리하는 방식이다.
    인스턴스가 생성되어있지않다면 synchronized 블록에서 생성하고 인스턴스가 생성되어 있다면 동기화는 실행되지 않고 생성되어있는 인스턴스를 반환한다.
public static Test getInstance(){
	if(instance==null){
    	synchronized (Test.class){
        	if(instance==null){
            	instance=new Test();
            }
        }
    }
    return instance;
}

그러나 코드만봤을땐 문제가 없는 방법이지만 컴파일 시 재배치 문제가 발생함.

DCL 재배치 문제
멀티스레드 환경에서 DCL방식으로 생성된 인스턴스는 특정 스레드가 synchronized 블록에 들어가기만해도 다른 스레드가 접근을 못하고 바로 인스턴스를 반환받으려고 하는데 이때 인스턴스가 생성되지않았다면 에러가 발생한다.

그래서 instance 변수에 volatile 키워드를 사용해야 메인 메모리 할당까지 끝난다음에야 다음 스레드가 접근할 수 있음.

  1. private 생성자, private static 내부 class안에서 private static final 인스턴스 new 생성변수. public static 메소드로 인스턴스 반환. jvm의 클래스 로더 메커니즘과 클래스 로드시점을 이용한 방식이다.

    JVM은 클래스 초기화과정에서 원자성을 보장함. final로 선언된 instance 변수는 원자성이 보장되었기때문에 최초 한번만 생성됨

내부 클래스를 통해 인스턴스 생성하면 쓰레드 동기화 문제가 해결된다. 최근에 가장 많이쓰는 방식

public class Test(){
	private Test();
    
    private static class InnerTest(){
    	private static final Test instance=new Test();
    }
    
    public static Test getInstance(){
    	return InnerTest.instance;
    }
}

1번처럼 원래 static 영역은 프로그램이 시작하자마자 바로 변수가 초기화되지만 Test클래스의 생성자를 private로 선언했기때문에 getInstance를 통해서 innetTest클래스를 참조하기전까진 InnerTest가 로딩되지않아서 instance가 할당되지않음.

LazyHolder기법이라고 합니다.. JVM의 원자성을 이용해 객체생성을 담당하는 내부클래스를 정의해 초기화하는 방식인것이져..

싱글톤 디자인 패턴을 언제 사용하는가

전역성을 띄는 하나의 인스턴스를 사용해 객체의 공유가 필요할 때.


친구들의 선정 주제는 스레드타입 스크립트. 둘다 현재 개발자로 근무하고 있기도하고 이제 막 java를 배우기 시작한 나에겐 상당히 어려운 용어들이 툭툭 나왔다 사실상 내가 공부한게 맞는건지 검증받는 시간이라고 할과,,😳

profile
6개월 국비과정 기록하기

0개의 댓글

Powered by GraphCDN, the GraphQL CDN