public class Main {
public static void main(String[] args) throws IOException {
Settings settings = new Settings();
Settings settings1 = new Settings();
System.out.println(settings!=settings1);
}
}
서로 다른 인스턴스이니까 true가 나온다
### 2. `new`를 사용하면 싱글톤을 만족할 수 없다. new를 사용하지 않으려면?
- `private`
- ![화면 캡처 2021-10-18 173138](https://user-images.githubusercontent.com/62214428/137696299-cda7feca-2fb8-453b-8df8-37f5b2ba59ef.png)
public class Main {
public static void main(String[] args) throws IOException {
Settings settings = Settings.getInstance();
Settings settings1 = Settings.getInstance();
System.out.println(settings!=settings1);
}
}
- 이 또한 내부에서 `new`를 사용하기 때문에 settings 와 settings 1은 다르다
- 우리는 이 두 인스턴스가 같기를 바란다
### 3. 두 인스턴스가 같길 바란다는 목적을 달성해보자
- ![화면 캡처 2021-10-18 173645](https://user-images.githubusercontent.com/62214428/137697167-cabfc4a9-dc81-4440-8df6-0ed0a5f81949.png)
- 이제 두 인스턴스는 항상 같다!!
### 4. 그럼 싱글톤을 완성한것인가?
- `아주 심각한 문제`
- `multithread`환경에서 과연 이 코드가 안전할까?
- ![화면 캡처 2021-10-20 115512](https://user-images.githubusercontent.com/62214428/138020621-90631a50-6e27-45a9-971d-12cd4dc58a9c.png)
- `파란색` 스레드가 `instance == null`을 체크하고 null이여서 `instance= new Settings()`에 진입하고 아직 새로운 인스턴스를 생성하지 않았는데 그 때 동시에 `빨간색` 스레드도 `if (instance == null)`을 체크하고 `instance= new Settings()`을 수행하게 된다면?
- 두 개의 인스턴스가 다르다. 그럼 싱글톤이 아니다.
### 5. 멀티쓰레드에서 안전하게 싱글톤을 완성하는 방법을 알아보자★
#### 1. synchronized 키워드 사용
![화면 캡처 2021-10-20 120019](https://user-images.githubusercontent.com/62214428/138021082-22f7383a-3776-4323-ae02-53a2bbbc9192.png)
- 문제점 : getInstance가 실행될 때 마다 동기화 관련 메커니즘이 실행되기 때문에 성능이슈가 있을 수 있다.
- 매 번 lock을 걸고 풀고 하는 과정이 있기 때문
public class Main {
public static void main(String[] args) {
MyThread myThread1 = new MyThread(1);
MyThread myThread2 = new MyThread(2);
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread {
public Settings settings;
private int num;
public MyThread(int num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
settings = Settings.getInstance();
System.out.println(settings + " thread " + num);
}
}
#### 2. 이른 초기화 / eager initialization
![화면 캡처 2021-10-20 120431](https://user-images.githubusercontent.com/62214428/138021474-ca604fb1-fbef-428f-834e-e8a15f5d48ce.png)
- 클래스가 로딩되는 시점에서 static한 필드들이 초기화되기 때문에 `INSTANCE`는 미리 만들어지고
- 그렇기 때문에 나중에 여러 스레드가 접근해도 결국 동일한 인스턴스를 return해주는 것
- 즉 스레드 세이프하다
- 다만 `미리 만들어둔다는게 단점일 수 있다`
- 만약 이 인스턴스가 매우크다. 그런데 만들어놓고 안쓰는 경우가 생긴다면?
#### 3. double checked locking
- 결론적으로 나는 `미리 만들어두지 않고` 그러나 `synchronized` 성능 이슈를 최소화하고 싶다.
- ![화면 캡처 2021-10-20 122145](https://user-images.githubusercontent.com/62214428/138022963-ca37a670-ea33-46af-928a-064b03b18297.png)
- 빨간색 스레드가 먼저` if (instance == null)`하고 들어가서 lock을 잡고 한 번 더 `if (instance == null)`한 후 `new Settings`로 인스턴스를 생성한다
- 이 때 파란색 스레드가 1번 `if (instance == null)`을 하고 와도 `synchronized`. 즉 빨간색 스레드가 `lock`을 잡고 있기 때문에 그 다음으로 넘어가지 않고 기다려야한다.
- 주의 `volatile` 키워드가 자바 1.5 이상부턴 필요하다
1,2번과 비교해보자
ㄱㅣ본적으로 ★ 미리 만들어두는 것이 아니라 필요할 때 생성한다 -2 번 개선
synchronized의 위치를 보자. 먼저 if (instance == null)체크로 한 번 걸러준다.
즉 한 번 걸러준 다음에 synchronized 매커니즘이 동작한다.
그래서 성능 이슈를 조금 더 줄일 수 있다 - 1번 개선
----------------
사실 3번은 매우 복잡한 코드다.
### 권장방법 1. static inner class
![화면 캡처 2021-10-20 122437](https://user-images.githubusercontent.com/62214428/138023359-65e8b474-ddbe-430e-b27b-624977b16248.png)
- 이렇게 되면 필요할 때 생성 lazy init★
- 멀티스레드에서도 안전하다.
멀티스레드에서도 안전하다. ? 왜?
static ★
static 필드들은 이미 클래스 로딩 시점에 생성되어 필드에 값이 채워져있다
그러니 getInstance()를 동시에 부르더라도 새로이 생성되는 것이 아니라
이미 만들어진 인스턴스를 부르고
그렇게 되면 절대 동시에 서로 다른 인스턴스가 생성될 수 없다
싱글톤 패턴
하나
만글로벌
하게 접근 가능해야한다.ex
spring bean
1.
new
를 사용하여 만든 인스턴스는 매 번 새로운 인스턴스를 생성서로 다른 인스턴스이니까 true가 나온다
public class Main { public static void main(String[] args) throws IOException { Settings settings = Settings.getInstance(); Settings settings1 = Settings.getInstance(); System.out.println(settings!=settings1); } }
public class Main { public static void main(String[] args) { MyThread myThread1 = new MyThread(1); MyThread myThread2 = new MyThread(2);
}
class MyThread extends Thread { public Settings settings; private int num; public MyThread(int num) { this.num = num; }
}
algo.Settings@74719816 thread 2 algo.Settings@74719816 thread 1 algo.Settings@74719816 thread 2 algo.Settings@74719816 thread 1 .....
1,2번과 비교해보자 ㄱㅣ본적으로 ★ 미리 만들어두는 것이 아니라 필요할 때 생성한다 -2 번 개선 synchronized의 위치를 보자. 먼저 if (instance == null)체크로 한 번 걸러준다. 즉 한 번 걸러준 다음에 synchronized 매커니즘이 동작한다. 그래서 성능 이슈를 조금 더 줄일 수 있다 - 1번 개선
멀티스레드에서도 안전하다. ? 왜? static ★ static 필드들은 이미 클래스 로딩 시점에 생성되어 필드에 값이 채워져있다 그러니 getInstance()를 동시에 부르더라도 새로이 생성되는 것이 아니라 이미 만들어진 인스턴스를 부르고 그렇게 되면 절대 동시에 서로 다른 인스턴스가 생성될 수 없다