Notice
Recent Posts
Recent Comments
07-05 04:18
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

Byeol Lo

Singleton 본문

BackEnd/Design Pattern

Singleton

알 수 없는 사용자 2024. 6. 29. 23:50
하나의 클래스는 오직 하나의 인스턴스만을 생성해야 한다.

 

 장점으로는 하나의 인스턴스를 기반으로 공유를 한다면 생성 비용도 줄며, 메모리를 공유할 수 있지만, 단점으로는 의존성이 높아지고, 테스트를 할 때 잘 설정해주어야 한다(한 인스턴스로만 하기 때문에 묶여 있어서 주의해야 함). 특히 DB를 연결한 후에 ORM 기반의 인스턴스를 하나로 운영하는 것도 singleton을 잘 활용한 예라고 할 수 있다.

 이렇게 한다면 다른 모듈들이 instance 생성 이라는 프로세스를 여러번 하지 않아도 미리 연결된 instance가 있고, 생성된 instance가 있기 때문에 굳이 RAM을 더 잡아먹어서 생성할 필요가 없다는 것이다(물론 상황에 따라 다르겠지만).

 밑은 그 예시다

public class Singleton {
    private static volatile Singleton instance;

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

    public void doSomething() {
        System.out.println("Singleton instance is doing something.");
    }
}

혹은 다음과 같이 생성할 수도 있다.

class Singleton {
    private static class singleInstanceHolder {
        pricate static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return singleInstanceHolder.INSTANCE;
    }
}

보통은 두번째 방법을 더 선호한다.

 

volatile: 모든 스레드가 직접 메인 메모리에서 읽고 쓸 수 있도록 하는 접근 제한자 비슷한 것으로 보면 되지만, 원자성을 보장하지 않기 때문에 synchronized 블록이나 ReentrantLock과 같은 강력한 synchronize mechanism이 필요하다. volatile이 방지하는 것은 기계어로 번역될 때의 명령어 재배치를 일관되게만 할 뿐이다. 그래서 volatile을 사용하는 목적은 그냥 해당 변수를 다른 스레드에서 보여주기 위해, 보기 위해서 사용하는 접근 제한자이다.

synchronized: critical section 을 설정하여 여러 스레드가 동시에 접근하지 못하도록 블록을 설정한다.

java.util.concurrent: AtomicInteger, AtomicLong 등의 클래스를 사용하여 원자적 연산을 보장한다.

Lock interface, 구현체 사용: ReentrantLock과 ReadWriteLock 등을 사용하여 더 세밀한 동기화 제어를 할 수 있다.

 

Lock interface, impl 예시

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// Shared resource class
class SharedResource {
    private int count = 0;
    private Lock lock = new ReentrantLock(); // ReentrantLock instance

    public void increment() {
        lock.lock(); // Acquire the lock
        try {
            count++; // Critical section: incrementing the shared resource
        } finally {
            lock.unlock(); // Release the lock
        }
    }

    public int getCount() {
        return count;
    }
}

// Main class to demonstrate usage
public class ReentrantLockExample {
    public static void main(String[] args) throws InterruptedException {
        final int THREAD_COUNT = 5;
        SharedResource sharedResource = new SharedResource();

        // Create threads that increment the shared resource
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    sharedResource.increment();
                }
            });
            threads[i].start();
        }

        // Wait for all threads to complete
        for (Thread thread : threads) {
            thread.join();
        }

        // Print the final count
        System.out.println("Final count: " + sharedResource.getCount());
    }
}

 

(OS에서 더 자세히 살펴볼 수 있습니다.)

'BackEnd > Design Pattern' 카테고리의 다른 글

Factory  (0) 2024.06.30
SOLID  (0) 2024.06.11
Comments