static과 싱글톤

static과 싱글톤에 대해 알아보자

static

static은 new 연산을 통해 생성한 객체와 달리 메모리에 한번 할당되면 프로그램이 종료하기 전까지 사라지지 않는다. 일반적으로 Class는 static 영역에 생성되고 new 연산을 통해 생성한 객체는 heap 영역에 생성된다. heap 영역은 GC(Garbage Collector)에 의해 관리되어 사용되지 않는 메모리를 해제시켜준다. 하지만 static 영역은 GC가 관리해 주지 않아 프로그램 종료까지 메모리가 할당된 채로 존재하므로 무분별하게 static을 사용하는 것은 좋지 않다.

Garbage Collector에 대해 더 알고 싶다면 다음 글을 참고하자!

static vs non-static

  • static: 클래스당 하나만 생성되며 동일한 클래스의 모든 객체들에 의해 공유된다.
    • non-static: 객체마다 별도로 존재한다.
  • static: 객체를 생성하기 전에 이미 생성되어 따로 객체를 생성하지 않아도 사용 가능하다.
    • non-static: 객체를 생성해야 사용가능하다.
  • static: 객체가 사라져도 사라지지 않으며 프로그램 종료시에 사라진다.
    • non-static: 객체와 생명주기가 같다.
public class Printer {
  public static String printStatic() {
      System.out.println("static 출력");
  }
  
  public String print() {
    System.out.println("일반 출력");
  }
}

Printer.printStatic();  //O (static 메소드로 객체를 생성하지 않아도 사용 가능)
Printer.print();        //X (static 메소드가 아니므로 객체 생성후 사용 가능)

Printer printer = new Printer();
printer.print();        //O

+ main이 static인 이유

main 메서드는 프로그램을 시작하는 부분으로 제일 먼저 실행되기 때문에 객체를 생성하지 않은 채로 작업을 수행할 수 있어야 되기 때문에 static이다.

실행 과정

  1. 코드를 실행하면 컴파일러가 자바 소스코드(.java)를 자바 바이트 코드(.class)로 변환
  2. Class Loader를 통해 class 파일을 메모리 영역에 로드
  3. Runtime Data Area 중 Method Area(=Class, Static area)라고 불리는 영역에 Class Variable이 저장되는데, static 변수도 여기 포함된다.
  4. JVM은 Method Area에 로드된 main()을 실행

JVM에 대해 더 상세하게 공부하고 싶다면 JVM 글를 참고하자!


싱글톤(Singleton)

static에 대해 알아봤으니 이제 static을 활용한 싱글톤 패턴에 대해 알아보자. 싱글톤 패턴이란 객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미한다. 싱글톤 패턴을 구현하는 방법에는 다양한 방법이 있지만 두 가지 정도만 알아보자.

Eager Initialization

public class Singleton {
  private static Singleton instance = new Singleton();
    
  private Singleton() {} // 생성자에 접근하지 못하도록 private

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

가장 간단한 방법으로 static을 통해 해당 클래스를 Class Loader가 로딩할 때 객체를 생성한다. 하지만 이렇게 구현하게 되면 이 객체를 사용하지 않더라도 객체가 무조건 생성되기 때문에 자원 낭비라는 단점이 있다.

Lazy Initialization

class Singleton {
	private static Singleton instance; 
	
	private Singleton() {} // 생성자에 접근하지 못하도록 private
	
	//객체가 존재하지 않으면 생성해주고 존재하면 기존 객체를 반환
	public static Singleton getInstance() {
		if (instance == null){
      instance = new Singleton();
    }
		return instance;
	}
}

이 방법 같은 경우는 첫 번째 방법에 자원의 낭비를 해결해 주지만 멀티 스레드 환경에서는 동기화 문제가 발생할 수 있다. 관심 있다면 나머지 방법들은 더 찾아서 공부해 보자!

그래서 싱글톤 패턴을 사용하면 뭐가 좋을까?

어떤 객체를 필요할 때마다 생성하는 것이 아니라 한 번만 생성해서 계속 활용하기 때문에 메모리도 아낄 수 있고 속도 측면에서도 좋다고 할 수 있다. 그리고 싱글톤 같은 경우는 static을 활용하여 전역으로 사용되는 인스턴스이기 때문에 쉽게 접근할 수 있다는 이점이 있다. 하지만 접근하기 쉽다는 것은 동시성 문제가 일어날 수도 있으니 조심해야 한다.

그럼 싱글톤에는 어떤 문제점들이 있을까?

싱글톤 패턴은 위와 같은 장점이 있지만 아래의 문제점들을 수반한다.

  1. 지저분한 구현 코드.
  2. private 생성자로 인해 상속이 불가능
  3. 테스트하기 어려움
    • 생성 방식 제한적 -> Mock, 동적 객체 주입 어려움
    • 인스턴스가 자원을 공유하고 있기 때문에 매번 상태 초기화
  4. 서버 환경에서 싱글톤이 한 개만 생성됨을 보장하지 못한다.
  5. 언제든지 접근할 수 있는 전역 상태로 사용되어 위험

싱글톤 컨테이너

위와 같이 많은 문제점이 있을 수 있기 때문에 안티 패턴으로도 불려 잘 고려해서 사용해야 한다. 하지만 스프링 컨테이너 같은 프레임워크의 힘을 빌려 싱글톤 패턴의 단점을 보완하면서 장점들도 같이 사용할 수 있다.

싱글톤 컨테이너에 대해 궁금하다면 싱글톤 컨테이너 글 참고

스프링 컨테이너가 객체를 생성하고 관리해 주어 앞선 싱글톤의 단점들을 제거해 준다.

  • 싱글톤 패턴을 위한 지저분한 코드 제거
  • private 생성자 제거
  • 객체지향적으로 개발 가능(DIP, OCP)
  • 프레임워크를 통해 1개의 객체 생성 보장

*틀린 부분이 있으면 언제든지 말씀해 주시면 공부해서 수정하겠습니다.


© 2022. All rights reserved.

Powered by 애송이