관련된 이전 포스트 의 내용을 간단히 요약하면
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
해당 클래스는 여러 스레드에서 getInstance
메서드가 호출하면 Singleton 타입의 변수 공유 변수
(singleton) 에 대해 동시성
문제가 발생합니다.
싱글톤
은 본래 객체가 하나만 생성되어 getInstance 메서드를 호출하는 모든 스레드에게 동일한 인스턴스
를 반환해야합니다. 하지만 현재의 getInstance 메서드나 공유 변수 (singleton) 는 어떠한 동기화 처리도 되어있지 않으며 이로인해 공유 변수가 null 일 때 여러 스레드가 getInstance 메서드를 동시에 호출하게 되면
if (singleton == null) {
singleton = new Singleton();
}
해당 if 문에 진입할 수 있으며 이로써 getInstance 를 호출한 스레드들은 각기 다른 인스턴스를 반환 받는 현상이 발생할 수 있습니다.
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
getInstance 메서드에 synchronized
키워드를 붙여 메서드를 동기화 시켰고 (정적 메서드이므로 해당 인스턴스를 동기화하는 것이 아닌 클래스 레벨로 동기화합니다.)
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
synchronized 위와 같이 synchronized 블록의 인자로 Singleton.class 를 주어 Singleton 클래스 자체를 동기화해 줄 수 있다.
http://tutorials.jenkov.com/java-concurrency/volatile.html
요약하자면 각 스레드는 메인 메모리
로부터 값을 복사해 CPU 캐시
에 저장하여 작업한다. CPU 가 2개 이상이라면 멀티 스레드 환경에서 각 스레드는 서로 다른 CPU 에서 동작하고 있으며 이는 각 스레드가 같은 변수에 대해 읽기, 쓰기 동작을 수행할 시 각자의 CPU 캐시
에 메인 메모리
의 값과 다른 값을 갖고 있을 수 있게 된다.
하지만 자바에서 어떠한 변수에 volatile
키워드를 붙이면 해당 변수는 모든 읽기와 쓰기 작업이 CPU 캐시가 아닌 메인 메모리
에서 이루어지게 되고 이로써 해당 변수 값에 대해 가시성
을 보장할 수 있다.
사실 이 가시성
이란 용어는 읽기, 쓰기 작업에 대해 '동시성 문제
를 해결해준다' 는 의미가 아니다. 메인 메모리에서 작업이 이루어진다는 이유로 해당 키워드만으로 동기화 처리가 이루어진다고 잘못 받아들이면 다음과 같이
private static Singleton singleton;
private volatile Singleton singleton;
단순히 static
-> volatile
키워드만 변경하고 synchronized 키워드로 동기화 처리는 하지 않아도 되는 것으로 오해할 수 있다.
하지만, 반드시 synchronized 키워드로 공유 객체에 대한 동기화 처리
까지 해주어야 싱글톤을 보장 받을 수 있다.
volatile
키워드를 사용하여 모든 스레드가 항상 같은 공유 변수의 값을 읽어올 수 있도록 보장한 뒤
- 어느 한 스레드가
synchronized
키워드를 사용한 getInstance 메서드 (하나의 스레드만이 접근할 수 있는 메서드) 로 공유 변수에 인스턴스를 할당하면
- volatile 키워드에 의해
메인 메모리
에 해당 인스턴스의 값이 갱신되고 이로써 다른 모든 스레드들은 null 이 아닌 공유 변수에 할당된 인스턴스를 메인 메모리로부터 바로 읽어 올 수 있게 된다.
- 공유 변수 값의 불일치가 일어나지 않기 때문에 다른 스레드가 또 다시 getInstance 의 if 문 블록에 진입하는 경우는 발생하지 않게 된다.
마지막으로 다음과 같이 작성한 getInstance 메서드는 항상 하나의 스레드만 접근
할 수 있어서 성능상의 문제가 있다.
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
synchronized 블록 내의 아래 조건식은 인스턴스를 최초로 할당하는 경우에만 필요한 로직이다.
if (singleton == null) {
singleton = new Singleton();
}
따라서, 다음과 같이 if 조건문으로 한 번 더 감싸줌으로써 인스턴스가 할당된 뒤에 다수의 스레드에서 해당 인스턴스를 동시에 참조
할 수 있도록 변경해주었다.
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
잘 보고 갑니다~😊