스프링에 대해 공부하게 되면서 스프링 컨테이너가 싱글톤 컨테이너의 역할을 하면서 스프링 컨테이너에 저장된 bean들을 싱글톤으로 관리된다고 배웠다.
요청이 올 때마다 객체를 생성하는 것이 아니라 이미 만들어진 객체를 공유해서 사용하는 것이다. 이렇게 되면 메모리 낭비를 방지할 수 있다.
하지만, 싱글톤 방식에서 주의할 점이 존재한다.
싱글톤 패턴이나, 싱글톤 컨테이너를 사용하는 경우, 인스턴스 하나를 공유하기 때문에 객체의 상태를 stateful하게 설계를 하면 안된다. 만약 필드가 공유되게 된다면 심각한 오류가 발생할 수 있다.
public class Hello {
private String hello; //공유되는 필드
private static Hello newInstance = new Hello();
private Hello() {
}
public static Hello getInstance() {
return newInstance;
}
public void print(String hello) {
this.hello = hello; // 파라미터를 필드로 저장
System.out.println("인사말 = " + hello + " " + "[" + Thread.currentThread().getName() + "]");
}
public String getHello() {
return hello;
}
}
public class Dutch implements Runnable {
@Override
public void run() {
//싱글톤 객체 생성
Hello hello4 = Hello.getInstance();
for (int i = 0; i < 100; i++) {
hello4.print("Hé");
}
}
}
public class English implements Runnable {
@Override
public void run() {
Hello hello2 = Hello.getInstance();
for (int i = 0; i < 100; i++) {
hello2.print("hello");
}
}
}
public class Japanese implements Runnable {
@Override
public void run() {
Hello hello3 = Hello.getInstance();
for (int i = 0; i < 100; i++) {
hello3.print("やあ");
}
}
}
public class Vietnamese implements Runnable {
@Override
public void run() {
Hello hello5 = Hello.getInstance();
for (int i = 0; i < 100; i++) {
hello5.print("Chào");
}
}
}
public class Main {
public static void main(String[] args) {
Hello korean = Hello.getInstance();
korean.print("안녕");
System.out.println("처음에 저장된 korean의 hello필드 = " + korean.getHello());
Thread english = new Thread(new English());
english.start();
Thread japan = new Thread(new Japanese());
japan.start();
Thread vietnam = new Thread(new Vietnamese());
vietnam.start();
Thread netherland = new Thread(new Dutch());
netherland.start();
for (int i = 0; i < 50; i++) {
System.out.println("======== korean의 필드 " + korean.getHello() + "===========");
//위에서 설정한 "안녕"이라는 값이 나오는지 확인
}
}
}
인사말 = 안녕 [main]
처음에 저장된 hello의 hello필드 = 안녕
인사말 = hello [Thread-0]
인사말 = hello [Thread-0]
인사말 = hello [Thread-0]
인사말 = hello [Thread-0]
인사말 = hello [Thread-0]
인사말 = hello [Thread-0]
...
인사말 = やあ [Thread-1]
인사말 = hello [Thread-0]
인사말 = やあ [Thread-1]
인사말 = Hé [Thread-3]
인사말 = Chào [Thread-2]
인사말 = hello [Thread-0]
...
======== korean의 필드 やあ===========
======== korean의 필드 Chào===========
======== korean의 필드 Chào===========
======== korean의 필드 Chào===========
======== korean의 필드 Chào===========
======== korean의 필드 Chào===========
======== korean의 필드 Chào===========
...
싱글톤으로 같은 인스턴스를 공유해서 쓰는 경우 여러 클라이언트가 동시에 같은 코드를 실행하는 경우가 발생한다.
처음에 main 스레드에서 print("안녕")를 실행하여 "안녕"을 Hello의 필드로 할당한다.
그 후 여러 다른 언어를 사용하는 스레드가 print()를 실행하면서 공유되는 인스턴스 필드를 다른 언어로 바꿔버린다.
JVM Stack
JVM Heap
public class Hello {
ThreadLocal<String> local = new ThreadLocal<>(); //스레드 로컬을 사용한다.
private static Hello newInstance = new Hello();
private Hello() {
}
public static Hello getInstance() {
return newInstance;
}
public void print(String hello) {
local.set(hello); //스레드 로컬에 파라미터를 저장한다.
System.out.println("인사말 = " + hello + " " + "[" + Thread.currentThread().getName());
}
}
class Main {
public static void main(String[] args) {
Hello korean = Hello.getInstance();
korean.print("안녕");
Thread english = new Thread(new English());
english.start();
Thread japan = new Thread(new Japanese());
japan.start();
Thread vietnam = new Thread(new Vietnamese());
vietnam.start();
Thread netherland = new Thread(new Dutch());
netherland.start();
for (int i = 0; i < 50; i++) {
System.out.println(
"========" + korean.local.get() + "==========="); //위에서 설정한 "안녕"이라는 값이 나오는지 확인
}
}
}
...
======== 안녕===========
======== 안녕===========
인사말 = Chào [Thread-2]
인사말 = Chào [Thread-2]
인사말 = Chào [Thread-2]
인사말 = Chào [Thread-2]
인사말 = Chào [Thread-2]
인사말 = Chào [Thread-2]
인사말 = Chào [Thread-2]
========안녕===========
========안녕===========
========안녕===========
========안녕===========
public class Hello {
private static Hello newInstance = new Hello();
private Hello() {
}
public static Hello getInstance() {
return newInstance;
}
public String print(String hello) {
String localHello = hello; //파라미터로 온 값을 로컬 변수에 저장하고 그것을 리턴한다.
System.out.println("인사말 = " + hello + " " + "[" + Thread.currentThread().getName());
return localHello;
}
}
Refernce