디자인패턴
디자인패턴
프그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계등을 이용하여 해결할 수 있도록 하나의 규약 형태로 만들어 놓은 것을 의미한다.
디자인 패턴을 사용해야 하는 이유 ?
효율적인 의사소통이 가능하다. 소통과정에서 오해의 소지를 줄이고, 패턴으로 패턴에 담겨 있는 모든 내용,특성,제약조건 등을 함께 이야기할 수 있다.
싱글턴패턴
싱글턴 패턴이란?
클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역접근을 제공한다.
싱글턴 패턴 장점
유일하다.
- 레스트리 설정 객체를 싱글턴 패턴을 이용하면 한 애플리케이션에 들어있는 어떤 객체에서도 같은 자원을 활용할 수 있다.
- 연결 풀이나 스레드 풀과 같은 자원 풀을 관리하는데 도움을 준다.
싱글턴 패턴이 유일함을 만족할 수 있는 이유 ?
생성자가 private로 지정되어 있기 때문이다. private 생성자로 클래스에 인스턴스를 생성하고, getInstance() 정적 메소드를 이용해서 호출할 수 있다.
싱글턴 패턴 특징
- 클래스에서 하나뿐인 인스턴스를 관리하도록 만들어야 한다.
- 다른 클래스에서 자신의 인스턴스를 추가로 생성하지 못하게 만들어야 한다.
- 생성자를 private로 지정하여, 클래스 내부에서만 인스턴스를 생성할 수 있다.
- 인스턴스가 필요하다면 반드시 클래스 자신을 거치도록 해야한다
- getInstance() 와 같은 메서드를 추가해서 만들어진 인스턴스를 전달할 수 있도록 해야한다.
싱글턴 패턴 클래스
public class Singleton {
//클래스의 하나뿐인 인스턴스를 저장하는 정적변수
private static Singleton uniqueInstance;
//private 생성자
private Singleton(){}
//인스턴스를 생성하고 전달한다.
public static Singleton getinstance(){
if(uniqueInstance == null){
uniqueInstance = new Singletone();
}
return uniqueInstance;
}
}
싱글턴 멀티스레딩 문제
예를 들어, 2개의 스레드가 getInstance() 정적 메서드를 이용할 때, 서로 다른 2개의 인스턴스가 생성될 수도 있다.
1번 스레드 | 2번 스레드 | uniqueInstance 값 |
---|---|---|
getInstance() 시작 | null | |
getInstance() 시작 | null | |
if(uniqueInstance == null) 조건문 시작 | null | |
if(uniqueInstance == null) 조건문 시작 | null | |
uniqueInstance = new Singletone() | object1 | |
return uniqueInstance; | object1 | |
uniqueInstance = new Singletone() | object2 | |
return uniqueInstance; | object2 | |
싱글턴 멀티스레딩 해결
synchronized
를 이용해서 임계영역을 설정할 수 있다. 임계영역을 통해 한번에 한 스레드만 이용할 수 있도록 한다. 그래서 1번 스레드의 메서드 실행이 끝나야지만, 2번 스레드가 메서드를 실행할 수 있다.
public class Singleton {
//클래스의 하나뿐인 인스턴스를 저장하는 정적변수
private static Singleton uniqueInstance;
//private 생성자
private Singleton(){}
//인스턴스를 생성하고 전달한다.
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singletone();
}
return uniqueInstance;
}
}
synchronized
를 이용한 멀티스레딩 문제 해결의 단점과 해결법
단점
가장 큰 문제점은 속도가 느리다는 것이다. 한번에 하나의 스레드만 처리하기 때문에, 멀티스레딩의 장점을 얻을 수 없다. 또한 어쩌면 병목지점이될지도 모른다.
해결법
큰 속도 저하가 일어나지 않고, 병목지점이 안된다면 그냥 두는 것도 하나의 방법이 될 수도 있지만, 속도 저하가 발생한다면 해결해야 한다.
1. 인스턴스를 필요할 때 생성하지 말고 처음부터 생성한다.
- 처음부터 인스턴스를 생성해두면, getInstance 메서드를 리턴만 하면 된다.
public class Singleton {
private static Singleton uniqueInstance = new Singletone();
private Singleton(){}
public static synchronized Singleton getInstance(){
return uniqueInstance;
}
}
2. DCL을 이용해서 getIsntance()에서 동기화되는 부분을 줄인다.
DCL(Double-Checked Locking)을 사용하면 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않을 때만 동기화한다.
- volatile : Java 변수를 Main Memory에 저장한다는 것을 명시하여 스레드의 변수값 불일치를 해결할 수 있다.
- volatile를 사용하지 않는다면, MainMemory에서 값을 불러와 CPU cache에 저장하고 읽기 때문
- synchronized : 현재 스레드를 제외하고 다른 스레드가 접근할 수 없도록 막음
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton(){}
public static synchronized Singleton getInstance(){
//인스턴스가 존재하지 않다면, 동기화된 블록으로 들어간다.
if(uniqueInstance == null){
synchronized(Singleton.class){
//한번 더 인스턴스가 생성되지 않았는지 확인한다.
if(uniqueInstance == null){
uniqueInstance = new Singletone();
}
}
}
return uniqueInstance;
}
}
3. final 이용
- 하지만 클래스를 사용하지 않아도 생성된다는, 단점이 있다
public class Singleton {
public static final Singleton uniqueInstance = new Singleton();
private Singleton(){};
}
직렬화와 역직렬화로 싱글턴 깨짐 현상
- 직렬화 : 객체 또는 데이터를 외부 자바 시스템에서 사용할 수 있도록 byte로 데이터 변환하는 기술
- 역직렬화 : 변환된 데이터를 다시 객체로 변환하는 기술
자바에서는 객체를 파일형태로 디스크에 저장할 때 직렬화해서 저장하고, 읽을 때 역직렬화를 하게 된다. 이때, 직렬화에 필요한 인스턴스와, 역직렬화를 통해 생성된 인스턴스가 서로 다르다. → 싱글턴 깨짐
리플렉션으로 싱글턴 깨짐 현상
- 리플렉션 : 구체적인 클래스 타입을 몰라도, 클래스의 메서드,타입,변수들에 접근할 수 있도록 해주는 자바 API이다.
리플렉션은 생성자를 호출할 수 있을뿐만 아니라, private 메서드,변수에도 접근할 수 있기 때문에 private 생성자로 만들어진 Singleton 패턴은 깨짐 현상이 나타날 수 있다.
Singleton singleton1 = Singleton.getInstance();
System.out.println(" singleton1 : "+ singleton1);
Class<Singleton> singleton = Singleton.class;
Constructor<Singleton> singletonConstructor = singleton.getDeclaredConstructor();
singletonConstructor.setAccessible(true);
Singleton singleton2 = singletonConstructor.newInstance();
System.out.println(" singleton2 : "+ singleton2);
//출력
singleton1 : Singleton@aec6354
singleton2 : Singleton@ee7d9f1
ENUM으로 싱글톤 생성하기
enum으로 싱글턴을 생성하면, 동기화 문제,직렬화와 역직렬화 문제, 리플렉션 문제를 해결할 수 있다.
Enum은 여러 인스턴스가 생성되지 않으며, JVM에서 Enum 타입은 클래스 로딩 시점에 단 한 번만 인스턴스화되기 때문이다.
하지만 실제 개발할때는 주로 쓰이지 않는듯...
public enum SingletonEnum {
UNIQUE_INSTANCE;
public static void printInstance() {
System.out.println(" singleton in enum : "+ UNIQUE_INSTANCE);
}
}
public class Main {
public static void main(String[] args) {
SingletonEnum instance1 = SingletonEnum.UNIQUE_INSTANCE;
SingletonEnum instance2 = SingletonEnum.UNIQUE_INSTANCE;
System.out.println("Instance 1 hashcode: " + instance1.hashCode());
System.out.println("Instance 2 hashcode: " + instance2.hashCode());
if (instance1 == instance2) {
System.out.println("Both instances are the same.");
} else {
System.out.println("Instances are different.");
}
}
}
//출력
Instance 1 hashcode: 1066376662
Instance 2 hashcode: 1066376662
Both instances are the same.
https://nesoy.github.io/articles/2018-06/Java-volatile
https://jeongkyun-it.tistory.com/225
https://inpa.tistory.com/entry/JAVA-☕-싱글톤-객체-깨뜨리는-방법-역직렬화-리플렉션#
꾸준히 기록할 수 있는 사람이 되자 !