Java (싱글톤(Singleton) 패턴 정리)

최병현·2026년 1월 1일

java

목록 보기
22/38

1. 싱글톤이 필요한 이유

프로그램을 만들다 보면 “어디서든 같은 객체 하나만 공유”해야 하는 경우가 있다.

  • 주차장/창고처럼 전역적으로 하나만 존재해야 하는 관리 객체
  • 설정값(Config) 관리 객체
  • 공통 자원(커넥션 풀, 로깅 등)을 대표하는 객체

만약 이런 객체를 new로 계속 생성하면, 관리 데이터가 각각 분리되어 흐름이 꼬이고 상태 관리가 어려워진다.

싱글톤 패턴은 이런 문제를 해결하기 위해 객체를 단 하나만 생성하고, 그 객체를 전역 공유하는 구조이다.


2. 싱글톤 핵심 구조

싱글톤은 보통 다음 3가지를 세트로 가진다.

  • private static instance: 유일한 객체를 저장하는 변수
  • private 생성자: 외부에서 new로 생성 못 하게 막음
  • public static getInstance(): 객체를 반환하는 유일한 통로

이 구조 덕분에 어떤 코드에서 접근하더라도 항상 “같은 객체”를 받게 된다.


3. DTO(Car) 클래스 (Lombok + Builder)

차량 정보를 담는 클래스이다. Lombok을 사용해서 반복 코드를 줄이고, Builder로 객체 생성 가독성을 올린 구조이다.

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@AllArgsConstructor
@Builder
public class Car {

    private int carNumber;
    private String company;
    private String model;
}

4. 싱글톤 클래스(KIA) 구현

KIA 클래스는 “주차장 관리소”처럼 동작한다.

주요 데이터는 다음과 같다.

  • carArray: 차량을 저장하는 배열(주차 공간 5칸)
  • autoCode: 차량번호 자동 증가 값

그리고 가장 중요한 포인트는 생성자를 private으로 막고, getInstance()로만 객체를 얻도록 만든 것이다.

public class KIA {

    private static KIA instance;

    private Car[] carArray;
    private int autoCode;

    private KIA() {
        carArray = new Car[5];
        autoCode = 20260000;
    }

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

    public Car inputCar(String company, String model) {
        Car car = Car.builder()
                .model(model)
                .company(company)
                .carNumber(++autoCode)
                .build();

        for (int i = 0; i < carArray.length; i++) {
            if (carArray[i] == null) {
                carArray[i] = car;
                System.out.println("입고 완료");
                return car;
            }
        }

        autoCode--;
        System.out.println("만차입니다.");
        return null;
    }

    public void outputCar(int carNumber) {
        for (int i = 0; i < carArray.length; i++) {
            Car car = carArray[i];
            if (car == null) continue;

            if (car.getCarNumber() == carNumber) {
                carArray[i] = null;
                System.out.println("출차 완료");
                return;
            }
        }
        System.out.println("차량 없음");
    }

    public void parkList() {
        boolean empty = true;

        for (int i = 0; i < carArray.length; i++) {
            Car car = carArray[i];
            if (car == null) continue;

            empty = false;
            System.out.println("번호: " + car.getCarNumber());
            System.out.println("제조사: " + car.getCompany());
            System.out.println("모델: " + car.getModel());
            System.out.println();
        }

        if (empty) {
            System.out.println("주차된 차량이 없습니다.");
        }
    }
}

5. getInstance()가 “같은 객체”를 보장하는 이유

getInstance()는 instance가 null일 때만 new KIA()를 실행한다.

  • 처음 호출: instance가 null → 객체 생성 → instance에 저장
  • 두 번째부터: instance가 null이 아님 → 기존 instance를 그대로 반환

그래서 아래처럼 여러 번 받아도 모두 같은 객체가 나온다.

public class SingletonMain {
    public static void main(String[] args) {
        KIA kia1 = KIA.getInstance();
        KIA kia2 = KIA.getInstance();
        KIA kia3 = KIA.getInstance();

        System.out.println(kia1);
        System.out.println(kia2);
        System.out.println(kia3);
    }
}

출력된 주소값(참조값)이 전부 같다면, 싱글톤이 정상적으로 동작하는 것이다.


6. 입고(inputCar) / 출차(outputCar) 흐름

6-1) inputCar(company, model)

  • Car.builder()로 차량 객체 생성
  • carNumber는 ++autoCode로 자동 증가
  • 배열에서 null인 공간(빈 칸)을 찾아 저장
  • 저장 성공: “입고 완료” 출력 + car 리턴
  • 만차: 실제 입고가 안 되었으므로 증가했던 번호를 되돌리고 “만차입니다.” 출력 + null 리턴

6-2) outputCar(carNumber)

  • 배열 전체를 순회하며 null이 아닌 칸만 검사
  • carNumber가 같은 차량을 찾으면 해당 칸을 null로 바꿔 출차 처리
  • 성공: “출차 완료”
  • 없으면: “차량 없음”

7. 예제 실행 코드(메인)

public class SingletonMain {
    public static void main(String[] args) {
        KIA kia1 =  KIA.getInstance();
        KIA kia2 =  KIA.getInstance();
        KIA kia3 =  KIA.getInstance();

        System.out.println(kia1);
        System.out.println(kia2);
        System.out.println(kia3);

        System.out.println(kia1.inputCar("kia", "morning"));
        System.out.println(kia1.inputCar("kia", "k3"));
        System.out.println(kia1.inputCar("kia", "k5"));
        System.out.println(kia1.inputCar("kia", "k7"));
        System.out.println(kia1.inputCar("kia", "k8"));
        System.out.println(kia1.inputCar("kia", "k9"));

        kia1.parkList();
        kia1.outputCar(20260001);

        System.out.println(kia1.inputCar("kia", "K7"));
        System.out.println(kia1.inputCar("kia", "K8"));
    }
}

8. 추가로 알아두면 좋은 주의점

  • 현재 getInstance() 구현은 학습용으로는 충분하지만, 멀티스레드 환경에서는 동시에 호출될 때 객체가 2개 생성될 가능성이 있다.
  • 실무에서는 thread-safe 싱글톤 구현(동기화, enum 싱글톤 등)도 존재한다.

9. static과 싱글톤의 관계

  • static 변수는 “클래스에 하나만 존재하는 공유 데이터”
  • 싱글톤은 “클래스에 하나만 존재하는 공유 객체”

즉, 싱글톤은 static을 이용해서 “유일 객체”를 저장하고 관리하는 패턴이다.


10. 오늘 정리하며 느낀 점

싱글톤은 단순히 “객체를 하나만 만들기”가 아니라, 프로그램 전체에서 같은 상태를 공유해야 하는 상황에서 구조를 안정적으로 유지하는 패턴이라는 걸 느꼈다.

특히 주차장처럼 데이터가 한 곳에 모여야 하는 구조에서는 new로 객체를 여러 번 만들면 관리 자체가 깨지기 때문에, getInstance()로 동일 객체를 공유하는 방식이 핵심이라는 점이 확실해졌다.

다음 단계로는 배열 대신 List로 확장하거나, 싱글톤의 스레드 안전성까지 고려하는 방식도 이어서 정리해보면 좋을 것 같다.

profile
Develop

0개의 댓글