λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Java

[Java, λ””μžμΈ νŒ¨ν„΄] - μ‹±κΈ€ν„΄ νŒ¨ν„΄(Singleton Pattern)

by 주발2 2021. 10. 24.
λ°˜μ‘ν˜•

πŸ“Ž  κΈ€λ˜ 6κΈ° ν¬μŠ€νŒ…

1. λ―ΈμΉ˜λ„λ‘ λ”μ› λ˜ 7μ›”μ˜ 회고

 

2. μ‚¬μš©μžκ°€ κ²Œμ‹œλ¬Όμ„ μž‘μ„±ν•  λ•Œμ˜ νŠΈλžœμž­μ…˜ 처리

 

3. Spring AOP - (1) ν”„λ‘μ‹œ νŒ¨ν„΄, λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄

 

4. [MySQL] - νŠΈλžœμž­μ…˜μ˜ κ²©λ¦¬ μˆ˜μ€€(Isolation level)

 

5. Spring AOP - (2) AOP κ°œλ… 및 μ‹€μŠ΅

 

6. μΈν…”λ¦¬μ œμ΄(IntelliJ) - 디버깅(Debugging) ν•˜κΈ°

 

7. [Java, λ””μžμΈνŒ¨ν„΄] - μ‹±κΈ€ν„΄ νŒ¨ν„΄(Singleton Pattern)

 

8. μ›”κ°„ μ½”λ“œλ¦¬λ·° Ver_0.1: 컀리어 μ„±μž₯ CODE μ„Έλ―Έλ‚˜ 정리

 

9. 포슀트맨(API ν…ŒμŠ€νŠΈ) ν™œμš©ν•˜κΈ°

 

10. λœ»κΉŠμ€ 2021λ…„ 회고


πŸ“Ž λ””μžμΈ νŒ¨ν„΄ - μ‹±κΈ€ν„΄ νŒ¨ν„΄(Singleton Pattern)

ν¬μŠ€νŒ…μ—μ„œ μž‘μ„±ν•œ 예제 μ½”λ“œλŠ” κΉƒν—ˆλΈŒμ—μ„œ ν™•μΈν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€ :)

 

μ•ˆλ…•ν•˜μ„Έμš”, 이번 μ‹œκ°„μ— 정리할 λ‚΄μš©μ€ λ””μžμΈ νŒ¨ν„΄μ—μ„œμ˜ μ‹±κΈ€ν„΄ νŒ¨ν„΄μž…λ‹ˆλ‹€. κ·Έλ™μ•ˆ λ””μžμΈ νŒ¨ν„΄μ˜ μ€‘μš”μ„±μ— λŒ€ν•΄μ„œλŠ” 자주 λ“€μ–΄μ™”μ—ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ λ””μžμΈ νŒ¨ν„΄μ„ ν•™μŠ΅ν•˜λ”λΌλ„ μ‹€μ œ κ°œλ°œμ„ μ§„ν–‰ν•˜λ©° λ””μžμΈ νŒ¨ν„΄μ„ 직접 μ μš©ν•΄λ³΄λŠ” 일이 μ—†μ—ˆκΈ°μ— λ‹€μ‹œ κΉŒλ¨Ήκ²Œλ˜λŠ” μ•…μˆœν™˜μ΄ λ°˜λ³΅λμ—ˆλŠ”λ°μš”, μ΄λ²ˆμ— λ””μžμΈ νŒ¨ν„΄μ— λŒ€ν•΄ 천천히 정리λ₯Ό ν•˜μ—¬ μ•žμœΌλ‘œ κ°œλ°œμ„ 진행할 λ•Œ μ μ ˆν•œ μƒν™©μ—μ„œ μ μ ˆν•œ νŒ¨ν„΄μ„ μ μš©ν•  수 μžˆλ„λ‘ 미리 ν•™μŠ΅ν•˜λŠ” 것을 λͺ©ν‘œλ‘œ ν•˜κ³ μž ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ””μžμΈ νŒ¨ν„΄μ„ μ‹€λ¬΄μ—μ„œ μ μš©ν•΄ κ°œλ°œμ„ μ§„ν–‰ν•˜λ €λ©΄ μ΅œμ†Œν•œ κ°œλ…μ •λ„λŠ” νŒŒμ•…μ„ ν•˜κ³  μžˆμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

 

 

 

🎯  λ””μžμΈ νŒ¨ν„΄


λ¨Όμ € μ‹±κΈ€ν„΄ νŒ¨ν„΄μ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄κΈ°μ „, λ””μžμΈνŒ¨ν„΄μ— λŒ€ν•΄ κ°„λž΅νžˆ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

λ‚˜λ¬΄μœ„ν‚€μ—μ„œ λ””μžμΈ νŒ¨ν„΄μ€ 객체 지ν–₯ ν”„λ‘œκ·Έλž˜λ° 섀계λ₯Ό ν•  λ•Œ 자주 λ°œμƒν•˜λŠ” λ¬Έμ œλ“€μ„ ν”Όν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” νŒ¨ν„΄ 이라고 μ •μ˜ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ””μžμΈ νŒ¨ν„΄μ— μ μ ˆν•œ κ²©μ–ΈμœΌλ‘œ '바퀴λ₯Ό λ‹€μ‹œ 발λͺ…ν•˜μ§€ 마라(Don't reinvent the wheel)' λΌλŠ” 말이 μžˆμŠ΅λ‹ˆλ‹€. 이 말은 이미 λ§Œλ“€μ–΄μ Έμ„œ 잘 λ˜λŠ” 것을 μ²˜μŒλΆ€ν„° λ‹€μ‹œ λ§Œλ“€ ν•„μš”κ°€ μ—†λ‹€λŠ” μ˜λ―Έμž…λ‹ˆλ‹€. 즉, λΆˆν•„μš”ν•˜κ²Œ μ²˜μŒλΆ€ν„° λ‹€μ‹œ μ‹œμž‘ν•  ν•„μš”κ°€ μ—†λ‹€λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€.

 

νŒ¨ν„΄(Pattern)μ΄λž€, 'μΌμ •ν•œ ν˜•νƒœμ˜ μ–‘μ‹μ΄λ‚˜ μœ ν˜•'을 μ˜λ―Έν•©λ‹ˆλ‹€. μ •λ¦¬ν•΄λ³΄μžλ©΄, μ–΄λ–€ λΉ„μŠ·ν•˜κ±°λ‚˜ λ™μΌν•œ 양식 λ˜λŠ” μœ ν˜•λ“€μ΄ λ°˜λ³΅λ˜μ–΄ λ‚˜νƒ€λ‚œλ‹€λŠ” μ˜λ―Έλ‘œλ„ 해석할 수 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ μ€‘μš”ν•œ 점은 λ°˜λ³΅λ˜μ–΄ λ‚˜νƒ€λ‚œλ‹€λŠ” μ˜λ―Έμž…λ‹ˆλ‹€. κ·Έλ™μ•ˆ κ°œλ°œμ„ μ§„ν–‰ν•˜λ©΄μ„œ κ·€κ°€ 닳도둝 λ“€μ–΄μ™”λ˜ 말 쀑 ν•˜λ‚˜κ°€ 'λ°˜λ³΅λ˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ 말라' μ˜€μŠ΅λ‹ˆλ‹€. λ‹€μ–‘ν•œ μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό κ°œλ°œν•  λ•Œ μ„œλ‘œκ°„μ— κ³΅ν†΅λ˜λŠ” 섀계 λ¬Έμ œκ°€ μ‘΄μž¬ν•˜λ©°, 이λ₯Ό μ²˜λ¦¬ν•˜λŠ” ν•΄κ²°μ±… 사이에도 곡톡점이 μ‘΄μž¬ν•©λ‹ˆλ‹€. 

 

λ””μžμΈ νŒ¨ν„΄μ˜ κ΅¬μ‘°λŠ” μ½˜ν…μŠ€νŠΈ(context), 문제(problem), ν•΄κ²°(solution) μ΄λΌλŠ” 3개의 ν•„μˆ˜μ μΈ μš”μ†Œλ‘œ ꡬ성이 λ©λ‹ˆλ‹€.

  • μ½˜ν…μŠ€νŠΈλž€, λ¬Έμ œκ°€ λ°œμƒν•˜λŠ” μ—¬λŸ¬ 상황을 κΈ°μˆ ν•©λ‹ˆλ‹€. 즉, νŒ¨ν„΄μ΄ 적용될 수 μžˆλŠ” 상황을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€. κ²½μš°μ— λ”°λΌμ„œλŠ” μ΄λŸ¬ν•œ νŒ¨ν„΄μ΄ μœ μš©ν•˜μ§€ λͺ»ν•œ 상황도 μ‘΄μž¬ν•©λ‹ˆλ‹€.
  • λ¬Έμ œλž€, νŒ¨ν„΄μ΄ μ μš©λ˜μ–΄ 해결될 ν•„μš”κ°€ μžˆλŠ” μ—¬λŸ¬ λ””μžμΈ μ΄μŠˆλ“€μ„ κΈ°μˆ ν•©λ‹ˆλ‹€. μ΄λ•Œ, μ—¬λŸ¬ μ œμ•½ 사항과 영ν–₯λ ₯도 λ¬Έμ œν•΄κ²°μ„ μœ„ν•΄ κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • ν•΄κ²°μ΄λž€, λ¬Έμ œλ₯Ό ν•΄κ²°ν•˜λ„λ‘ 섀계λ₯Ό κ΅¬μ„±ν•˜λŠ” μš”μ†Œλ“€κ³Ό κ·Έ μš”μ†Œλ“€ μ‚¬μ΄μ˜ 관계, μ±…μž„, ν˜‘λ ₯ 관계λ₯Ό κΈ°μˆ ν•©λ‹ˆλ‹€.

λ””μžμΈ νŒ¨ν„΄μ˜ ꡬ성 μš”μ†Œ

 

 

 

πŸ“  μ‹±κΈ€ν„΄ νŒ¨ν„΄


μ•žμ„œ λ””μžμΈ νŒ¨ν„΄μ— λŒ€ν•΄ κ°„λž΅νžˆ μ„€λͺ…을 ν–ˆμ—ˆλŠ”λ°μš”, 이제 μ‹±κΈ€ν„΄ νŒ¨ν„΄μ— λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

μ‹±κΈ€ν„΄ νŒ¨ν„΄μ΄λž€, μΈμŠ€ν„΄μŠ€κ°€ 였직 ν•˜λ‚˜λ§Œ μƒμ„±λ˜λŠ” 것을 보μž₯ν•˜κ³  μ–΄λ””μ„œλ“  λ™μΌν•œ μΈμŠ€ν„΄μŠ€μ— μ ‘κ·Όν•  수 μžˆλ„λ‘ ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μž…λ‹ˆλ‹€.

μ‹±κΈ€ν„΄ νŒ¨ν„΄μ€ 생성 νŒ¨ν„΄μ˜ ν•œ μ’…λ₯˜λ‘œ, 생성 νŒ¨ν„΄μ€ 객체 생성에 κ΄€λ ¨λœ νŒ¨ν„΄μœΌλ‘œ 객체의 생성과 쑰합을 μΊ‘μŠν™”ν•΄ νŠΉμ • 객체가 μƒμ„±λ˜κ±°λ‚˜ λ³€κ²½λ˜μ–΄λ„ ν”„λ‘œκ·Έλž¨ ꡬ쑰에 영ν–₯을 크게 받지 μ•ŠλŠ” μœ μ—°μ„±μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

 

μΈμŠ€ν„΄μŠ€κ°€ 였직 ν•˜λ‚˜λ§Œ μƒμ„±λ˜μ–΄μ•Ό ν•˜λŠ” 상황을 κ°€μ •ν•˜κΈ° μœ„ν•΄, ν˜„μ‹€μ„Έκ³„μ—μ„œ ν”„λ¦°ν„°λ₯Ό μ‚¬μš©ν•˜λŠ” μ˜ˆμ‹œλ₯Ό λ“€μ–΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

AλΌλŠ” νšŒμ‚¬λŠ” ν˜„μž¬ μ½”λ‘œλ‚˜λ‘œ 인해 사정이 λ„‰λ„‰μΉ˜ μ•ŠκΈ° λ•Œλ¬Έμ— μ˜ˆμ‚°μ„ μ΅œλŒ€ν•œ 쀄여야 ν•©λ‹ˆλ‹€. 이λ₯Ό μœ„ν•΄ μ‚¬λ‚΄μ—μ„œ μ‚¬μš©λ˜λŠ” ν”„λ¦°ν„°λŠ” ν•œ λŒ€λ₯Ό μ œμ™Έν•˜κ³  λͺ¨λ‘ μ²˜λΆ„μ„ ν•΄λ²„λ ΈμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ λͺ¨λ“  직원듀은 λ™μΌν•œ ν”„λ¦°ν„° ν•œ λŒ€λ§Œ κ³΅μœ ν•΄μ„œ μ‚¬μš©μ„ ν•΄μ•Ό ν•©λ‹ˆλ‹€.

이λ₯Ό κ°„λ‹¨ν•˜κ²Œ μ½”λ“œλ‘€ λ‚˜νƒ€λ‚΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

public class Printer {

    private static Printer printer;

    // μΈμŠ€ν„΄μŠ€ν™”λ₯Ό λ§‰λŠ”λ‹€.
    private Printer() { }

    // μ‹±κΈ€ν„΄ νŒ¨ν„΄(단일 μΈμŠ€ν„΄μŠ€)
    public static Printer getInstance() {
        if (printer == null) {
            printer = new Printer();
        }
        return printer;
    }

    public void print(final String message) {
        System.out.println(message);
    }
}


public class User {
    private final String name;

    public User(String name) {
        this.name = name;
    }

    public void print() {
        Printer printer = Printer.getInstance();
        printer.print(this.name + " print using " + printer);
    }
}


import org.junit.jupiter.api.Test;

class PrinterTest {

    @Test
    void 단일_μŠ€λ ˆλ“œ_ν”„λ¦°ν„°() {
        final int userNumber = 5;
        User[] users = new User[userNumber];

        for (int i = 0; i < userNumber; i++) {
            users[i] = new User((i + 1) + "-user");
            users[i].print();
        }
    }
}

ν˜„μž¬ Printer 클래슀의 κ°μ²΄λŠ” private을 톡해 μ™ΈλΆ€μ—μ„œ 생성이 λΆˆκ°€λŠ₯ν•˜κ³  λ‚΄λΆ€μ˜ getInstance() λ©”μ†Œλ“œλ₯Ό ν†΅ν•΄μ„œλ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.

λ˜ν•œ μœ„ κ²°κ³Όλ₯Ό 보면 5개의 Printer μΈμŠ€ν„΄μŠ€κ°€ μ „λΆ€ λ™μΌν•˜κ²Œ 좜λ ₯이 λ˜λŠ”κ²ƒμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

β€» 보톡, μ‹±κΈ€ν„΄ νŒ¨ν„΄μ„ λ‚˜νƒ€λ‚Όλ•Œ λ©”μ†Œλ“œλŠ” getInstance()λΌλŠ” 이름을 μ‚¬μš©ν•˜κ³€ ν•©λ‹ˆλ‹€.

 

λ”°λΌμ„œ μœ„μ™€ 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄ λ˜λŠ” 것 κ°™μ§€λ§Œ, μ‹€μ œλ‘œλŠ” 문제점이 많이 μ‘΄μž¬ν•©λ‹ˆλ‹€. 

μ•„λž˜μ—μ„œ μ’€ 더 μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

 

πŸ“Œ  문제점


μœ„ ν”„λ¦°ν„°λŠ” λͺ¨λ“  μΈμŠ€ν„΄μŠ€κ°€ λ™μΌν•˜κΈ° λ•Œλ¬Έμ— μ‹±κΈ€ν„΄ νŒ¨ν„΄μ„ λ§Œμ‘±ν•˜λŠ” 것 처럼 λ³΄μ΄μ§€λ§Œ, μ‹€μ œλ‘œλŠ” 그렇지 μ•ŠμŠ΅λ‹ˆλ‹€.

λ©€ν‹° μŠ€λ ˆλ“œμ˜ ν™˜κ²½μ—μ„œλŠ” λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλŠ”λ°μš”, λ‹€μŒκ³Ό 같은 μ‹œλ‚˜λ¦¬μ˜€κ°€ μ‘΄μž¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • Printer μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ§€ μ•Šμ•˜μ„ λ•Œ μŠ€λ ˆλ“œ Aκ°€ getInstance() λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•΄ μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜μ—ˆλŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.
  • μœ„μ—μ„œ μŠ€λ ˆλ“œ Aκ°€ μƒμ„±μžλ₯Ό ν˜ΈμΆœν•΄ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€κΈ° μ „, μŠ€λ ˆλ“œ Bκ°€ getInstance() λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•΄ if문을 μ‹€ν–‰ν•œλ‹€λ©΄, ν˜„μž¬ Printer μΈμŠ€ν„΄μŠ€λŠ” nullμ΄λ―€λ‘œ μΈμŠ€ν„΄μŠ€κ°€ 생성이 λ©λ‹ˆλ‹€.
  • λ§ˆμ°¬κ°€μ§€λ‘œ μŠ€λ ˆλ“œ A도 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€κ°€ 생성이 λ©λ‹ˆλ‹€.

μœ„ μ‹œλ‚˜λ¦¬μ˜€λŠ” κ²½ν•© 쑰건(race condition)을 λ°œμƒν‚€λŠ”λ°, κ²½ν•© μ‘°κ±΄μ΄λž€ λ©”λͺ¨λ¦¬μ™€ 같은 λ™μΌν•œ μžμ›μ„ 2개 μ΄μƒμ˜ μŠ€λ ˆλ“œκ°€ μ΄μš©ν•˜λ €κ³  κ²½ν•©ν•˜λŠ” ν˜„μƒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.

 

λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œμ˜ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

public class Printer {
   
    ...

    public static Printer getInstance() {
        if (printer == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ignored) { }
            printer = new Printer();
        }
        return printer;
    }   
    
    
public class UserThread extends Thread {

    public UserThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        Printer printer = Printer.getInstance();
        printer.print(Thread.currentThread().getName() + " print using " + printer);
    }
}


    @Test
    void 닀쀑_μŠ€λ ˆλ“œ_ν”„λ¦°ν„°() {
        UserThread[] userThreads = new UserThread[NUMBER];

        for (int i = 0; i < NUMBER; i++) {
            userThreads[i] = new UserThread((i + 1) + "-thread");
            userThreads[i].start();
        }
    }

Printer 클래슀의 getInstance() λ©”μ†Œλ“œμ—μ„œ μŠ€λ ˆλ“œ 싀행을 고의적으둜 1msλ™μ•ˆ μ •μ§€ν•˜λ„λ‘ μ„€μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

κ·Έ ν›„ Threadλ₯Ό μƒμ†λ°›λŠ” UserThread 클래슀λ₯Ό μƒμ„±ν•˜κ³ , ν…ŒμŠ€νŠΈλ₯Ό 해보면 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

μœ„ λ©€ν‹° μŠ€λ ˆλ“œμ˜ κ²°κ³Όλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ λͺ¨λ‘ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€κ°€ 생성이 λ˜λŠ”κ±Έ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

 

πŸ”‘  해결방법


객체의 이λ₯Έ μ΄ˆκΈ°ν™”(eager initialization) μ‚¬μš©ν•˜κΈ°

객체λ₯Ό lazyν•˜κ²Œ μƒμ„±ν•˜λŠ” λŒ€μ‹  미리 μƒμ„±ν•˜λŠ”(이λ₯Έ μ΄ˆκΈ°ν™”) 방법이 μ‘΄μž¬ν•©λ‹ˆλ‹€.

public class Printer {

    private static final Printer INSTANCE = new Printer();

    private Printer() { }

    public static Printer getInstance() {
        return INSTANCE;
    }
}

μœ„ κ²°κ³Όλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ λͺ¨λ‘ λ™μΌν•œ μΈμŠ€ν„΄μŠ€κ°€ 생성이 λ˜λŠ”κ²ƒμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μœ„ 방법 λ˜ν•œ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λŠ” 과정이 였래걸리고 λ©”λͺ¨λ¦¬λ₯Ό 많이 μ‚¬μš©ν•˜λŠ”λ° λ°˜ν•΄ 객체λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ μ΄λ˜ν•œ 문제점이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

synchronized ν‚€μ›Œλ“œ μ‚¬μš©ν•˜κΈ°

public static synchronized Printer getInstance() {
  ...

μœ„μ™€ 같이 μ‚¬μš©ν•˜κ³ μž ν•˜λŠ” λ©”μ†Œλ“œμ— synchronized ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

β€» synchronized ν‚€μ›Œλ“œ 외에도 λ³€μˆ˜μ— volatile ν‚€μ›Œλ“œλ‚˜ Atomic 클래슀λ₯Ό μ΄μš©ν• μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

 

κ²°κ³Όλ₯Ό 확인해보면 λͺ¨λ‘ λ™μΌν•œ μΈμŠ€ν„΄μŠ€κ°€ 생성이 λ˜λŠ”κ±Έ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

β€» synchronized ν‚€μ›Œλ“œμ— λŒ€ν•΄μ„œλŠ” μ•„λž˜ ν¬μŠ€νŒ…μ— μžμ„Ένžˆ μ„€λͺ…이 λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ μœ„μ™€ 같이 λ©”μ†Œλ“œ 블둝에 synchronizedλ₯Ό 톡해 동기화λ₯Ό ν•˜λ©΄ 극단적인 κ²½μš°μ—μ„œλŠ” μ„±λŠ₯이 100λ°° 이상 μ €ν•˜λ  수 μžˆλ‹€κ³  ν•©λ‹ˆλ‹€.

좜처: https://en.wikipedia.org/wiki/Double-checked_locking

 

 

double check locking

μœ„μ™€ 같은 λ¬Έμ œμ μ€ double check lockingμ΄λΌλŠ” 방법을 톡해 ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

double check lockingμ΄λž€, 이름을 톡해 νŒŒμ•…μ„ν•΄λ³΄λ©΄ locking을 더블 μ²΄ν¬ν•˜λŠ” κ°œλ…μž…λ‹ˆλ‹€.

public class Printer {

    private static Printer printer;

    private Printer() { }

    public static Printer getInstance() {
        if (printer == null) {
            synchronized (Printer.class) {
                if (printer == null) {
                    printer = new Printer();
                }
            }
        }
        return printer;
    }
}

λ©”μ†Œλ“œ λΈ”λ‘μ—μ„œ synchronizedλ₯Ό ν†΅ν•œ λ™κΈ°ν™”λŠ” ν•΄λ‹Ή λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ 동기화λ₯Ό ν•˜λŠ” 반면, μœ„ μ½”λ“œλŠ” λ©”μ†Œλ“œ λ‚΄λΆ€μ—μ„œ Printer μΈμŠ€ν„΄μŠ€κ°€ null일 λ•Œλ§Œ 동기화λ₯Ό μ„€μ •ν•˜κ²Œ λ©λ‹ˆλ‹€. 

λ”°λΌμ„œ λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ 동기화λ₯Ό ν•˜λŠ” μ½”λ“œλ³΄λ‹¨ μ„±λŠ₯λ©΄μ—μ„œ 이점이 μžˆμŠ΅λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ μœ„ μ½”λ“œλ„ 문제점이 μ‘΄μž¬ν•œλ‹€κ³  ν•˜λŠ”λ°μš”, μœ„ν‚€λ¬Έμ„œμ—μ„œλŠ” λ‹€μŒκ³Ό 같이 이야기λ₯Ό ν•˜κ³ μžˆμŠ΅λ‹ˆλ‹€.

  1. Thread A notices that the value is not initialized, so it obtains the lock and begins to initialize the value.
  2. Due to the semantics of some programming languages, the code generated by the compiler is allowed to update the shared variable to point to a partially constructed object before A has finished performing the initialization. For example, in Java if a call to a constructor has been inlined then the shared variable may immediately be updated once the storage has been allocated but before the inlined constructor initializes the object.
  3. Thread B notices that the shared variable has been initialized (or so it appears), and returns its value. Because thread B believes the value is already initialized, it does not acquire the lock. If B uses the object before all of the initialization done by A is seen by B (either because A has not finished initializing it or because some of the initialized values in the object have not yet percolated to the memory B uses (cache coherence)), the program will likely crash.

μœ„μ—μ„œ μ–ΈκΈ‰ν•˜κ³  μžˆλŠ” λ¬Έμ œμ μ— λŒ€ν•΄ μ €μ˜ ν˜„μž¬ μ§€μ‹μœΌλ‘œλŠ” μ΄ν•΄ν•˜κΈ° νž˜λ“  뢀뢄이 λ§Žμ§€λ§Œ, μ œκ°€ μ΄ν•΄ν•œ λ°”λ‘œλŠ” λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ μŠ€λ ˆλ“œ A와 μŠ€λ ˆλ“œ Bκ°€ μΈμŠ€ν„΄μŠ€μ˜ 생성이 λ˜μ§€ μ•Šμ•˜μœΌλ‚˜ 생성이 λ˜μ—ˆλ‹€κ³  νŒλ‹¨ν•˜λŠ” μΈμŠ€ν„΄μŠ€ 생성 및 μ΄ˆκΈ°ν™” κ³Όμ •μ—μ„œμ˜ 좩돌이 λ°œμƒν•  수 μžˆλ‹€κ³  λ§ν•˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€.

(잘λͺ»λœ λ‚΄μš©μ΄ μžˆλ‹€λ©΄ λ§μ”€ν•΄μ£Όμ‹œλ©΄ κ°μ‚¬ν•˜κ² μŠ΅λ‹ˆλ‹€ (__) πŸ˜‚)

 

λ”°λΌμ„œ μœ„ μ½”λ“œμ—μ„œ printer λ³€μˆ˜μ— volatile을 μ„ μ–Έν•΄μ£Όλ©΄ 해결이 λ©λ‹ˆλ‹€.

private static volatile Printer printer;

...

volatile ν‚€μ›Œλ“œλŠ” μžλ°” λ³€μˆ˜λ₯Ό '메인 λ©”λͺ¨λ¦¬μ— μ €μž₯ ν•΄μ•Όν• 'ν‘œμ‹μœΌλ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ volatile은 λ³€μˆ˜μ˜ κ°€μ‹œμ„±(Visibility)을 보μž₯ν•˜λŠ”λ°μš”, volatile ν‚€μ›Œλ“œμ— λŒ€ν•΄ μžμ„Έν•œ λ‚΄μš©μ€ μ•„λž˜ 링크λ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”.

 

ν•˜μ§€λ§Œ μœ„μ™€ 같은 μ½”λ“œλ„ Java 1.4κΉŒμ§€λŠ” λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.(Java 17이 λ‚˜μ˜¨ λ§ˆλ‹Ήμ— java 1.4κ°€ μžˆμ„κΉŒ μ‹ΆκΈ΄ ν•©λ‹ˆλ‹€λ§Œ..)

Java 1.4κΉŒμ§€ volatile ν‚€μ›Œλ“œλŠ” 이전에 μ½νžˆκ±°λ‚˜ 쓰인 값이 μ»΄νŒŒμΌλŸ¬μ— μ˜ν•΄ μž¬μ •λ ¬μ΄ 될 수 있고, 이둜 인해 double check lockingμ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€κ³  ν•©λ‹ˆλ‹€. μ΄λŠ” Java 1.5λΆ€ν„°λŠ” 변경이 λ˜μ—ˆκΈ°λ•Œλ¬Έμ—, Java 1.5λΆ€ν„°λŠ” 신경쓰지 μ•Šμ•„λ„ λ©λ‹ˆλ‹€.

 

 

static classλ₯Ό ν†΅ν•œ 지연 μ΄ˆκΈ°ν™”

μœ„μ—μ„œ μ‚΄νŽ΄λ΄€λ˜ 해결책듀은 κ½€λ‚˜ λ³΅μž‘ν•œ λ°©λ²•λ“€μž…λ‹ˆλ‹€. Java 버전을 ν™•μΈν•΄μ•Όν•˜λ©°, volatile ν‚€μ›Œλ“œμ™€ synchronized ν‚€μ›Œλ“œμ— λŒ€ν•΄μ„œλ„ μŠ΅λ“ν•˜κ³  μžˆμ–΄μ•Ό μ½”λ“œλ₯Ό μ΄ν•΄ν•˜κ³  μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

그럼 μ’€ 더 κ°„νŽΈν•œ 방법은 μ—†μ„κΉŒμš”? πŸ€” μ•„λž˜ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

public class Printer {

    private Printer() { }

    private static class PrinterHolder {
        public static final Printer printer = new Printer();
    }

    public static Printer getInstance() {
        return PrinterHolder.printer;
    }
}

 

μœ„ μ½”λ“œλŠ” λ‚΄λΆ€ 클래슀인 PrinterHolderκ°€ 호좜이 될 λ•ŒκΉŒμ§€ λ‘œλ“œκ°€ λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” 사싀에 μ˜μ‘΄ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œλ„ μ•ˆμ „ν•˜κ³ , getInstance() λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ PrinterHolder 클래슀λ₯Ό ν˜ΈμΆœν•˜κΈ° λ•Œλ¬Έμ— 지연 μ΄ˆκΈ°ν™”λ„ κ°€λŠ₯ν•˜κ²Œ λ©λ‹ˆλ‹€. λ˜ν•œ 이전에 μ‚΄νŽ΄λ΄€λ˜ double check locking와 같이 μ½”λ“œκ°€ λ³΅μž‘ν•˜μ§€ μŠ΅λ‹ˆλ‹€.

 

μ‹€μ œ μœ„ μ½”λ“œλ₯Ό 기반으둜 ν…ŒμŠ€νŠΈλ₯Ό 해보면 μœ„μ™€κ°™μ΄ λͺ¨λ‘ λ™μΌν•œ μΈμŠ€ν„΄μŠ€κ°€ 생성이 λ©λ‹ˆλ‹€.

 

 

λ©€ν‹° μŠ€λ ˆλ“œμ—μ„œ μžμ›μ„ κ³΅μœ ν•˜λŠ” static class

λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ μžμ›μ„ κ³΅μœ ν•˜λ©΄ 동기화가 λ˜λŠ”μ§€ ν…ŒμŠ€νŠΈν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

public class StaticPrinter {

    private static int counter = 0;

    public synchronized static void print(final String str) {
        counter++;
        System.out.println(str + " " + counter);
    }
}


public class StaticUserThread extends Thread {

    public StaticUserThread(String name) {
        super(name);
    }

    public void run() {
        StaticPrinter.print(Thread.currentThread().getName() + " print using ");
    }
}


class StaticPrinterTest {

    private static final int NUMBER = 5;

    @Test
    void μžμ›μ„_κ³΅μœ ν•˜λŠ”_λ©€ν‹°_μŠ€λ ˆλ“œ() {
        StaticUserThread[] user = new StaticUserThread[NUMBER];

        for (int i = 0; i < NUMBER; i++) {
            user[i] = new StaticUserThread((i + 1) + "-thread");
            user[i].start();
        }
    }
}

μœ„ μ½”λ“œμ˜ κ²°κ³Όλ₯Ό 보면 counter λ³€μˆ˜κ°€ 1μ”© μ¦κ°€ν•˜λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

그럼 항상 static classλ₯Ό μ‚¬μš©ν•˜λ©΄ 될까?

μ—¬λŸ¬ μžλ£Œλ“€μ„ μ°Ύμ•„λ³΄λ‹ˆ 항상 κ·Έλ ‡μ§€λŠ” μ•Šλ‹€κ³  ν•©λ‹ˆλ‹€.

정적 클래슀λ₯Ό μ΄μš©ν•˜λŠ” 방법과 μ‹±κΈ€ν„΄ νŒ¨ν„΄μ„ μ΄μš©ν•˜λŠ” 방법쀑 κ°€μž₯ 차이가 λ‚˜λŠ” 점은 객체λ₯Ό μ „ν˜€ μƒμ„±ν•˜μ§€ μ•Šκ³  정적 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•œλ‹€λŠ” μ μΈλ°μš”(μ½”λ“œκ°€ κΆκΈˆν•˜μ‹œλ©΄ java.lang.Math 클래슀λ₯Ό μ°Έκ³ ν•΄λ³΄μ‹œλ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.) μœ„ κ²°κ³Όμ—μ„œ 확인할 수 μžˆλ“―μ΄ 문제 없이 counter λ³€μˆ˜κ°€ μŠ€λ ˆλ“œ 5κ°œμ—μ„œ μ•ˆμ „ν•˜κ²Œ κ³΅μœ λ˜μ–΄ μ‚¬μš©λ  수 μžˆμŒμ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. λ”μš±μ΄ 정적 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ 일반적으둜 μ‹€ν–‰ν•  λ•Œ λ°”μΈλ”©λ˜λŠ”(컴파일 νƒ€μž„ 바인딩) μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λŠ” 것보닀 μ„±λŠ₯λ©΄μ—μ„œλ„ μš°μˆ˜ν•˜λ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ‹±κΈ€ν„΄ νŒ¨ν„΄μ˜ 경우 static classμ™€λŠ” 달리 객체 내뢀에 κ³΅μœ ν•˜λŠ” μžμ›μ΄ μžˆμ„λ•Œ μœ μš©ν•œλ°, 예λ₯Όλ“€μ–΄ λ°μ΄ν„°λ² μ΄μŠ€λ‚˜ in-memory μΊμ‹œ λ“±κ³Ό 같은 μƒνƒœλ“€μ΄ μžˆμŠ΅λ‹ˆλ‹€. 즉, ν”„λ‘œκ·Έλž¨μ˜ μ—¬λŸ¬ κ³³μ—μ„œ μœ„μ™€ 같은 곡유 μžμ›λ“€μ„ μ‚¬μš©ν•˜κΈ°λ₯Ό μ›ν•˜κ³ , 단일 지점(single point)λ₯Ό ν†΅ν•˜μ—¬ μ΄λŸ¬ν•œ μžμ›λ“€μ— λŒ€ν•΄ μ ‘κ·Όν•˜κ³ μž ν•  λ•Œ μœ μš©ν•˜λ‹€κ³  ν•©λ‹ˆλ‹€.

 

μœ„μ˜ λ‚΄μš©λ“€μ€ μ œκ°€ κ²½ν—˜ν•΄λ³΄μ§€λŠ” λͺ»ν•œ μ•„λž˜ 링크듀을 μ°Έκ³ ν•˜μ—¬ μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν‹€λ¦° λ‚΄μš©μ΄ μžˆλ‹€λ©΄ ν”Όλ“œλ°± λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.

 

 

 

✏️  정리


λ””μžμΈ νŒ¨ν„΄ 및 μ‹±κΈ€ν„΄ νŒ¨ν„΄μ— λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€. κ·Έλ™μ•ˆ 싱글톀 νŒ¨ν„΄μ€ λ‹¨μˆœνžˆ 'μΈμŠ€ν„΄μŠ€κ°€ 였직 ν•˜λ‚˜λ§Œ μƒμ„±λœλ‹€.' λΌλŠ” κ°œλ…λ§Œ μ•Œκ³  μ§€λ‚΄μ™”μœΌλ©° μΈμŠ€ν„΄μŠ€λ₯Ό μ™ΈλΆ€μ—μ„œ 생성할 수 없도둝 μƒμ„±μžλ₯Ό private둜 막고 정적 λ©”μ†Œλ“œ(getInstance)λ₯Ό 톡해 μΈμŠ€ν„΄μŠ€λ₯Ό 생성할 수 μžˆλ‹€. λΌλŠ” μ •λ„λ‘œλ§Œ μ•Œκ³ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이번 정리λ₯Ό 톡해 μ‹±κΈ€ν„΄ νŒ¨ν„΄μ— λŒ€ν•΄ μ’€ 더 깊게 κ³΅λΆ€ν•˜κ³ μž μ—¬λŸ¬ μžλ£Œλ“€κ³Ό μ˜μƒλ“€μ„ μ°Έκ³ ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 

ν¬μŠ€νŒ…μ—μ„œ μ„€λͺ…λ“œλ¦° κ²ƒμ²˜λŸΌ μ‹±κΈ€ν„΄ νŒ¨ν„΄μ„ κ΅¬ν˜„ν•˜λŠ” 방식에도 μ—¬λŸ¬κ°€μ§€κ°€ μ‘΄μž¬ν•˜λ©°, 이와 κ΄€λ ¨ν•΄μ„œ synchronized, volatile와 같은 ν‚€μ›Œλ“œλ‚˜ double check locking λ“±λ“±μ˜ μš©μ–΄μ— λŒ€ν•΄μ„œλ„ ν•™μŠ΅ν•  수 μžˆλŠ” μ‹œκ°„μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

 

 

 

 

πŸ“  References

 

λ°˜μ‘ν˜•

λŒ“κΈ€