JUINTINATION

싱글턴(Singleton) 패턴 본문

JAVA객체지향디자인패턴

싱글턴(Singleton) 패턴

DEOKJAE KWON 2023. 7. 5. 22:05
반응형

싱글턴 패턴이란?

'단 하나의 원소만을 가진 집합'이라는 수학 이론에서 유래된 인스턴스가 딱 하나만 생성되고 어디에서든 이 인스턴스에 접근할 수 있도록 보장하는 패턴이다.

클래스에 싱글턴 패턴을 적용하는 방법은 다음과 같다.

  1. 생성자를 private 메서드로 변경한다.
class Singleton {
    private Singleton() {}
}
  1. 접근할 하나의 인스턴스를 static 인스턴스로 만들어준다.
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
}
  1. 하나의 static 인스턴스에 접근하기 위한 static 메서드를 만든다.
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        return instance == null ? new Singleton() : instance;
    }
}

예제 코드를 만들어 확인해보면 결과는 다음과 같다.

public class Main {
    private static final int USER_NUM = 5;
    public static void main(String[] args) {
        User[] user = new User[USER_NUM];
        for (int i = 0; i < USER_NUM; i++) {
            user[i] = new User("User" + i);
            user[i].print();
        }
    }
}

class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
    public void print() {
        Printer printer = Printer.getPrinter();
        printer.print(this.name + " is using " + printer.toString());
    }
}

class Printer {
    private static Printer printer = null;
    private Printer() {}
    public static Printer getPrinter() {
        if (printer == null) {
            printer = new Printer();
        }
        return printer;
    }
    public void print(String str) {
        System.out.println(str);
    }
}
User0 is using Printer@19dfb72a
User1 is using Printer@19dfb72a
User2 is using Printer@19dfb72a
User3 is using Printer@19dfb72a
User4 is using Printer@19dfb72a
  • 위 코드의 문제점
    • 다중 스레드에서 Printer 클래스의 인스턴스가 1개 이상 생성될 가능성이 존재한다.
      1. Printer 클래스의 인스턴스가 아직 생성되지 않았을 때 스레드 1이 getPrinter 메서드의 if문을 실행해 인스턴스가 생성되어있는 상태인지 확인한다. 현재 printer 변수는 null인 상태이다.
      2. 만약 스레드 1이 생성자를 호출해 인스턴스를 만들기 전 스레드 2가 if문을 실행해 인스턴스가 생성되어있는 상태인지 확인한다. 현재 printer 변수는 null인 상태이므로 생성자를 호출하는 코드를 실행한다.
      3. 스레드 1도 스레드 2와 마찬가지로 인스턴스를 생성하는 코드를 실행하게 되면 결과적으로 Printer 클래스의 인스턴스가 2개 생성된다.
public class Main {
    private static final int USER_NUM = 5;
    public static void main(String[] args) {
        UserThread[] user = new UserThread[USER_NUM];
        for (int i = 0; i < USER_NUM; i++) {
            user[i] = new UserThread("User" + i);
            user[i].start();
        }
    }
}

class UserThread extends Thread {
    public UserThread(String name) {
        super(name);
    }
    public void run() {
        Printer printer = Printer.getPrinter();
        printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
    }
}

class Printer {
    private static Printer printer = null;
    private Printer() {}
    public static Printer getPrinter() {
        if (printer == null) {
            // 의도적 지연
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            printer = new Printer();
        }
        return printer;
    }
    public void print(String str) {
        System.out.println(str);
    }
}
User2 is using Printer@1133ea76
User0 is using Printer@7b6ade42
User3 is using Printer@506d0b61
User1 is using Printer@506d0b61
User4 is using Printer@35dd8ea8

이 때 다음과 같이 프린터 인스턴스에 유지해야 하는 값이 있다면 문제가 발생할 수 있다.

public class Main {
    private static final int USER_NUM = 5;
    public static void main(String[] args) {
        UserThread[] user = new UserThread[USER_NUM];
        for (int i = 0; i < USER_NUM; i++) {
            user[i] = new UserThread("User" + i);
            user[i].start();
        }
    }
}

class UserThread extends Thread {
    public UserThread(String name) {
        super(name);
    }
    public void run() {
        Printer printer = Printer.getPrinter();
        printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
    }
}

class Printer {
    private static Printer printer = null;
    private int cnt = 0;
    private Printer() {}
    public static Printer getPrinter() {
        if (printer == null) {
            // 의도적 지연
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            printer = new Printer();
        }
        return printer;
    }
    public void print(String str) {
        System.out.println(str + " and cnt is " + (++cnt));
    }
}
User3 is using Printer@35dd8ea8 and cnt is 1
User4 is using Printer@5463113d and cnt is 1
User1 is using Printer@6a0fc530 and cnt is 1
User0 is using Printer@1ff40620 and cnt is 1
User2 is using Printer@4d0b6d80 and cnt is 1

이를 해결하기 위해 다음과 같은 방법을 사용할 수 있다.

  1. 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법
  2. 인스턴스를 만드는 메서드에 동기화하는 방법

방법 1

public class Main {
    private static final int USER_NUM = 5;
    public static void main(String[] args) {
        UserThread[] user = new UserThread[USER_NUM];
        for (int i = 0; i < USER_NUM; i++) {
            user[i] = new UserThread("User" + i);
            user[i].start();
        }
    }
}

class UserThread extends Thread {
    public UserThread(String name) {
        super(name);
    }
    public void run() {
        Printer printer = Printer.getPrinter();
        printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
    }
}

class Printer {
    private static Printer printer = new Printer();
    private int cnt = 0;
    private Printer() {}
    public static Printer getPrinter() {
        return printer;
    }
    public void print(String str) {
        System.out.println(str + " and cnt is " + (++cnt));
    }
}
User2 is using Printer@5eaaef1c and cnt is 5
User4 is using Printer@5eaaef1c and cnt is 4
User3 is using Printer@5eaaef1c and cnt is 3
User0 is using Printer@5eaaef1c and cnt is 1
User1 is using Printer@5eaaef1c and cnt is 2

방법 2

public class Main {
    private static final int USER_NUM = 5;
    public static void main(String[] args) {
        UserThread[] user = new UserThread[USER_NUM];
        for (int i = 0; i < USER_NUM; i++) {
            user[i] = new UserThread("User" + i);
            user[i].start();
        }
    }
}

class UserThread extends Thread {
    public UserThread(String name) {
        super(name);
    }
    public void run() {
        Printer printer = Printer.getPrinter();
        printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
    }
}

class Printer {
    private static Printer printer = null;
    private int cnt = 0;
    private Printer() {}
    public synchronized static Printer getPrinter() {
        if (printer == null) {
            printer = new Printer();
        }
        return printer;
    }
    public void print(String str) {
        System.out.println(str + " and cnt is " + (++cnt));
    }
}
User2 is using Printer@5eaaef1c and cnt is 4
User0 is using Printer@5eaaef1c and cnt is 5
User1 is using Printer@5eaaef1c and cnt is 1
User4 is using Printer@5eaaef1c and cnt is 2
User3 is using Printer@5eaaef1c and cnt is 3

cnt 변수 또한 동기화를 해주면 순서대로 1, 2, 3, 4, 5를 출력할 수 있다.

public class Main {
    private static final int USER_NUM = 5;
    public static void main(String[] args) {
        UserThread[] user = new UserThread[USER_NUM];
        for (int i = 0; i < USER_NUM; i++) {
            user[i] = new UserThread("User" + i);
            user[i].start();
        }
    }
}

class UserThread extends Thread {
    public UserThread(String name) {
        super(name);
    }
    public void run() {
        Printer printer = Printer.getPrinter();
        printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
    }
}

class Printer {
    private static Printer printer = null;
    private int cnt = 0;
    private Printer() {}
    public synchronized static Printer getPrinter() {
        if (printer == null) {
            printer = new Printer();
        }
        return printer;
    }
    public void print(String str) {
        synchronized (this) {
            System.out.println(str + " and cnt is " + (++cnt));
        }
    }
}
User2 is using Printer@3bf8335e and cnt is 1
User3 is using Printer@3bf8335e and cnt is 2
User0 is using Printer@3bf8335e and cnt is 3
User1 is using Printer@3bf8335e and cnt is 4
User4 is using Printer@3bf8335e and cnt is 5

싱글턴 패턴 컬레보레이션

 

싱글턴 패턴을 적용하기 위한 컬레보레이션이다.

생성자는 private로,

instance 객체는 private static으로,

getInstance 메서드는 public static으로 선언한다.

 

 

728x90

'JAVA객체지향디자인패턴' 카테고리의 다른 글

스테이트(State) 패턴  (0) 2023.07.06
스트래티지(Strategy) 패턴  (0) 2023.07.06
디자인패턴 개요  (0) 2023.07.03
좋은 객체 지향 설계의 5가지 원칙 (SOLID)  (0) 2023.07.02
객체 지향 원리  (0) 2023.07.02
Comments