본문 바로가기
Java

Java_ Lazy Singleton

by JunsC 2024. 8. 25.
728x90

싱글톤 사용은 유용하다고 느꼈다.

앱을 만들때 공통적으로 사용하는 api 라든지 , 메소드 등 반복적인 사용이 필요한 부분들이 있는데 이걸 계속 생성자로 생성해주거나 단순히 메모리에 올려 전역으로 사용한다면 성능상 문제가 생길것이다.

이러한 부분을 방지하기 위해 싱글톤이 있었고 싱글톤은 메모리에 올림으로 인한 누수 나 지속적인 생성사용을 방지한다.

이때, lazy singleton 방식이 있는데 이는 기존 싱글톤 방식에서 더욱 효율적인 방법으로 메모리를 다루는다고 보면 된다.

밑의 코드는 기본적인 싱글톤 코드이다.

public class Singleton {

private static Singleton instance;

private Singleton() {

 }

public static synchronized Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

}

return instance;

}

 

synchronized 로 동기화를 적용하고 있기에 스레드 관련해서 안정성을 부여했다.

 

그리고 밑에 코드는 아까 언급한 lazy_singleton 방식이라 생각하면 된다.

더블체크를 하는 기능을 추가한것이다.

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

 }

 

 

혹은 내가 사용하고 있는 Inner class 방식이다.

public class Singleton {

private static class SingletonHolder {

private static final Singleton INSTANCE = new Singleton();

}

private Singleton() {

}

public static Singleton getInstance() {

return SingletonHolder.INSTANCE;

}  

}

 

 

자 이렇게 위처럼 싱글톤에 대해 대략적인 내용을 살펴보았다. 위의 설명은 싱글톤에 대해 어떤 느낌인지 주려고 만든 예제 내용이다. 싱글톤에 대해 다시한번 정리해보도록 하자 !

 

싱글톤(Singleton) 패턴이란?

객체의 인스턴스를 단 하나만 생성하고, 어디서든 동일한 인스턴스를 사용하도록 보장하는 디자인 패턴

1. 왜 싱글톤을 사용할까?

  • 전역적으로 하나의 객체를 공유해야 할 때 사용
  • 객체가 여러 개 생성되는 것을 방지하여 메모리를 절약
  • 데이터를 일관되게 유지 (예: 설정 정보, 데이터베이스 연결, 로그 관리 등)

 

2. 싱글톤 구현 방법 (Java)

① 기본적인 싱글톤 구현 (Lazy Initialization)

public class Singleton {
    private static Singleton instance; // 유일한 인스턴스 저장

    private Singleton() {} // private 생성자로 외부에서 객체 생성 방지

    public static Singleton getInstance() {
        if (instance == null) { // 최초 호출 시 객체 생성
            instance = new Singleton();
        }
        return instance;
    }
}
 

💡 특징:

  • 필요할 때만 객체를 생성 (Lazy Initialization)
  • 단일 인스턴스 유지
  • 멀티스레드 환경에서는 동시 접근 문제가 발생할 수 있음

 

 

② Thread-safe 싱글톤 (synchronized)

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

💡 synchronized 키워드를 사용하여 멀티스레드 환경에서도 안전하게 만듦.
⚠️ 하지만, synchronized는 성능 저하를 유발할 수 있음.

 

 

③ 이른 초기화 (Eager Initialization)

public class Singleton {
    private static final Singleton instance = new Singleton(); // 미리 생성

    private Singleton() {}

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

 

💡 특징:

  • 클래스가 로드될 때 인스턴스를 생성 (항상 메모리를 차지함)
  • 멀티스레드에서 안전하지만, 객체를 사용하지 않더라도 미리 생성됨

 

④ DCL(Double-Checked Locking) 방식 (권장)

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

💡 특징:

  • volatile을 사용하여 CPU 캐시 문제 방지
  • synchronized 블록을 최소화하여 성능 개선
  • 가장 효율적인 싱글톤 구현 방식 중 하나

 

 

⑤ 정적 내부 클래스 (권장)

 

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

 

💡 특징:

  • 클래스가 로드될 때 SingletonHolder 내부 클래스는 로드되지 않음
  • getInstance()가 호출될 때만 INSTANCE가 생성됨 (Lazy Initialization)
  • 스레드 안전하며, 성능도 좋음 (추천 방식)

 

 

3. 싱글톤 사용 예시

① 로그 관리

public class Logger {
    private static Logger instance;

    private Logger() {}

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }

    public void log(String message) {
        System.out.println("[LOG]: " + message);
    }
}

// 사용
Logger.getInstance().log("앱이 시작됨!");

 

② 데이터베이스 연결 관리

public class DatabaseConnection {
    private static DatabaseConnection instance;

    private DatabaseConnection() {}

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }

    public void connect() {
        System.out.println("데이터베이스 연결됨!");
    }
}

// 사용
DatabaseConnection.getInstance().connect();

 

 

4. 싱글톤의 장점 & 단점

장점

  • 메모리 절약 (객체를 하나만 생성)
  • 데이터 일관성 유지 (모든 곳에서 동일한 객체 사용)
  • 전역 접근 가능 (어디서든 쉽게 호출)

단점

  • 멀티스레드 환경에서 주의 필요 (잘못 구현하면 동시 접근 문제 발생)
  • 전역 상태 공유로 인해 코드가 복잡해질 수 있음
  • 테스트하기 어려움 (Mocking이 어려움)

5. 싱글톤을 언제 사용할까?

사용할 때

  • 로그 관리
  • 데이터베이스 연결
  • 설정 값 저장 (예: 앱 환경 설정)
  • 네트워크 요청 관리

사용하지 말아야 할 때

  • 많은 상태를 관리하는 경우 (전역 상태 오염 가능)
  • 단일 책임 원칙(SRP)에 위배될 가능성이 있을 때
  • 멀티스레드에서 동시성 이슈가 중요한 경우

 

■ 결론

  • 싱글톤은 객체를 하나만 유지해야 하는 경우 유용
  • 멀티스레드 환경에서 올바르게 구현해야 안전함
  • 너무 남용하면 코드 복잡도 증가 & 테스트 어려움
  • DCL 방식 또는 정적 내부 클래스 방식이 가장 추천됨! 🚀

 

이제부터 제대로 싱글톤에 대해 사용해보도록 하자 !!!

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."