본문 바로가기

안드로이드/기본기 다지기

싱글톤 패턴(Kotlin, java)

728x90

싱글톤

프로그램에서 클래스의 인스턴스를 단 하나만 만들어야 할 때 사용되는 디자인패턴

사용예시로는 데이터베이스를 변경할 수 있는 DBHandler 클래스의 인스턴스가 두 개 있어서 동시에 데이터베이스에 접근한다면 문제가 생길텐데(데이터베이스 동시성문제) 이를 방지할 수도 있습니다.

 

자바에서 싱글톤 구현

public class DBHandler { 
    private static DBHandler instance;
    
    // 생성자 접근 차단
    private DBHandler(){}
    
    public static DBHandler getInstance() { 
        if(instance == null) { 
            instance = new DBHandler();
        } 
        return instance; 
    } 
}

먼저 private로 외부에서 생성자에 접근을 막고

getInstance를 통해 인스턴스를 만들도록 합니다.

이때 static instance를 확인하여 인스턴스가 없으면 객체를 새로 만들고, 있다면 그대로 반환해주는 구조입니다.

 

코틀린에서 싱글톤 구현

object DBHandler {...}
val dbHandler = DBHandler

코틀린에서는 object키워드를 사용하여 간단하게 싱글톤을 생성할 수 있습니다.

언어 차원에서 object 키워드로 생성한 인스턴스는 초기화시 한번만 실행되어 Tread-safe하다는 것이 보장됩니다.

따라서 싱글톤을 만들기 위해서는 따로 패턴을 만들필요 없이 object만 사용하면됩니다.

다만 object를 사용하면 인스턴스를 생성할 때 파라미터를 전달할 수 없다는 한계가 있습니다.

파라미터를 전달하기 위해서는 결국 다음과 같이 클래스를 구성해서, 자바의 static을 companion object로 구현해야합니다.

Thread-safe? -> 

 

클래스로 구현

class DBHandler private constructor(context: Context) {
    companion object {
        private var instance: DBHandler? = null

        fun getInstance(context: Context) =
            instance ?: DBHandler(context).also {
                instance = it
            }
    }
}

위의 자바코드와 동일한 작동을 하는 코틀린 코드입니다.

이때 생성자는 context를 전달받을 수 있게 구성하였습니다.

 

위의 구조대로면 하나의 Tread에서 instance에 접근시에 null이면 객체를 생성하고 null이 아니면 instance를 반환하여 하나의 instance 객체를 갖게되므로 싱글톤패턴을 만족합니다.

그런데 두 개의 Tread에서 instance에 접근시에는 문제가 발생합니다. 두 Tread가 보기에 instance가 null값일경우 두 Tread 각각 하나씩의 객체를 생성하게 되므로 두

개의 객체를 생성하여 싱글톤 패턴이 깨지게 됩니다.

이러한 문제를 해결하기 위한 방법 중 하나로는 Double Checked Locking(DCL) 방법이 있습니다.

 

Double Checked Locking(DCL)

class DBHandler private constructor(context: Context) {
    companion object {
        @Volatile
        private var instance: DBHandler? = null

        fun getInstance(context: Context) =
            instance ?: synchronized(DBHandler::class.java) {
                instance ?: DBHandler(context).also {
                    instance = it
                }
            }
    }
}

8번 라인에서 인스턴스를 생성하기전 7번 라인에서 synchronized를 사용해 여러 스레드가 동시에 경합하지 않도록 막아줍니다.(Tread가 동시에 접근하는것이 문제이기 때문)

7번 라인에서 synchronized를 실행하기 전 instance의 null 체크를 한 번 더 해줍니다.

null 체크를 하지 않으면 각 Tread에서 getInstance에 접근할 때 인스턴스가 이미 존재하면 instance를 반환하여야 하는데, synchronized에 의해 일단 lock이 걸려 성능저하가 발생할 수 있습니다.

그래서 null체크를 통해 null일때문 synchronized이하 블록을 실행하도록 한 것입니다.

이렇게 두 번 instance를 체크하기 때문에 Double Checked Loking이라 합니다.

 

그런데 이때  instance 변수에는 Volatile 어노테이션을 붙였는데 그 이유는 

Tread는 메인 메모리와 독립된 Stack 메모리 공간을 할당받습니다.

그래서 Tread가 객체를 참조할 때 객체의 메인 메모리를 바로 보는게 아닌 메인 메모리에서 읽어온 내용을 스레드의 Stack메모리에 저장한 후 이것을 보게 되어있습니다.

이로 인해 인스턴스를 인식하는데 시차가 발생하게 됩니다.

 

시차가 발생하게되면 메인 메모리에서 null인 instance 객체를 1번 Tread에서 확인하고 instance화 하여도 2번 Tread에서 확인한 instance는 아직 null로 보여서 instance를 만드는 일이 발생할 수 있습니다.

이때 instance에 Volatile 어노테이션을 붙여 Tread가 메인메모리에서 직접 instace를 참조하게 되므로(Stack메모리에 저장하는것이 아닌) 시차가 발생하지않아 싱글톤이 깨지는 문제를 회피할 수 있습니다.

 

DCL에 대해서는 여러가지 논란이 있습니다만 그래도 이 구조는 현재 구글의 Room 라이브러리나, 코틀린의 lazy 함수에서도 사용되는 코드이기 때문에 안심하고 사용하셔도 될 것 같습니다.

 

다만 메인메모리를 직접 사용하면서 Tread를 잠그는 방식은 처리성능에 영향을 줄 수 있는데, 자바에는 

Volatile+synchronized 조합을 사용하지 않아도 Tread-safe한 싱글톤을 만드는 Bill Pugh Solution이라는 방법이 있습니다.

 

Bill Pugh Solution

여기서는 Inner static helper class를 사용합니다,

예를 들어 두 개의 Tread가 getInstance를 실행했다고 가정했을 때

1번 Tread에 의해 실행된 getInstance는 holder 클래스를 통해 싱글톤을 생성합니다.

이 작업중 2번 Tread에 의해 getInstance를 실행해도 인스턴스는 private static final로 되어있어 1번 Tread의 작업완료를 기다립니다.

 

public class DBHandler {

    private DBHandler() {}
    
    private static class Holder{
        private static final DBHandler INSTANCE = new DBHandler();
    }
    
    public static DBHandler getInstance() {
        return Holder.INSTANCE;
    }
}

위 코드를 아래같이 코틀린으로 변환할 수 있지만 내부 클래스가 object가 되어 생성자를 사용할 수 없어 차라리 DBHandler를 object로 인스턴스화 하는 것이 낫다.

class DBHandler private constructor() {
    companion object {
        val instance: DBHandler by lazy { Holder.INSTANCE }
    }

    private object Holder {
        val instance = DBHandler()
    }
}

 

 

 

 

 

 

출처 : https://cliearl.github.io/posts/android/understanding-singleton-pattern/

 

알기쉬운 Singleton Pattern

이번 포스팅에서는 싱글톤 패턴에 대해 알아보도록 하겠습니다. Singleton 이란 싱글톤(Singleton)은 소프트웨어 디자인패턴의 한 종류로, 프로그램 안에서 클래스의 인스턴스가 단 하나만 존재해야

cliearl.github.io

 

출처(영상) : https://www.youtube.com/watch?v=jzeQQeosBHA