📌 싱글톤 패턴(Singleton)
⭐ 개념
- 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
- 주로 DB연결 모듈에 많이 사용
⭐ 장점
- 하나의 인스턴스를 기반으로 다른 모듈들이 공유해서 사용하기 때문에 비용이 줄어든다.
- 데이터 공유가 쉽다. 싱글톤 인스턴스가 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다.
⭐ 단점
- 의존성이 높아진다.
- TDD(Test Driven Development)를 할 때 걸림돌이 된다. 싱글톤 인스턴스는 자원을 공유하고 있기 때문에 테스트가 결정적으로 격리된 환경에서 수행되려면 매번 인스턴스의 상태를 초기화시켜주어야 한다. 그렇지 않으면 어플리케이션 전역에서 상태를 공유하기 때문에 테스트가 온전하게 수행되지 못한다
⭐ 코드(JDBC)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBConn
{
private static Connection dbConn;
public static Connection getConnection() throws ClassNotFoundException, SQLException{
if (dbConn == null){
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String user = "scott";
String pwd = "pcwk";
Class.forName("oracle.jdbc.driver.OracleDriver");
dbConn = DriverManager.getConnection(url, user, pwd);
}
return dbConn;
}
public static Connection getConnection(String url, String user, String pwd) throws ClassNotFoundException, SQLException{
if (dbConn == null){
Class.forName("oracle.jdc.driver.OracleDriver");
dbConn = DriverManager.getConnection(url, user, pwd);
}
return dbConn;
}
}
⭐ 코드(Java)
class Singleton {
private static class singleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return singleInstanceHolder.INSTANCE;
}
}
public class Main{
public static void main(String []args){
Singleton a = Singleton.getInstance();
Singleton b = Singleton.getInstance();
System.out.println(a.hashCode());
System.out.println(b.hashCode());
if (a == b){
System.out.println(true);
}
}
}
⭐ 싱글톤 패턴을 구현하는 방법
💡 1. 단순 메소드 호출
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
단점
- 메소드의 원자성이 결여
- 자바는 멀티스레드 환경을 고려하기 때문에 인스턴스를 2개 이상 만들 수 있음.
ex) 쓰레드 t1, t2를 만들고 각각 실행을 시키게 되면 t1-1 => t1-2 => t2-1 => t2-3
이런식으로 꼭 실행될거 같지만, t1-1 => t2-1 => t1-2 => t2-2
식으로 실행 될 수도 있다. 이러한 경우 인스턴스가 2개가 만들어져서 싱글톤이 되지 못한다.
💡 2. synchronized
- 인스턴스를 반환하기 전에
synchronized
키워드로 잠금을 할 수 있다. 최초로 접근한 스레드가 메소드 호출시에 다른 스레드가 접근하지 못하게 잠금(lock)
을 건다.
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
단점
getInstance()
메소드를 호출할 때마다 lock이 걸려있어서 성능저하 발생
💡 3. 정적(static) 멤버, 블록
- static 멤버나 블록은 최초 JVM이 클래스를 로드할 때 미리 인스턴스를 생성하는 방법
public class Singleton {
private final static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
단점
- 싱글톤 인스턴스가 필요 없는 경우에도 무조건 호출해 인스턴스를 만들기 때문에 자원낭비가 발생
💡 4. 정적멤버와 중첩 클래스(Lazy Holder)
- 중첩클래스라는 내부클래스를 하나 더 만듬으로써, 최초에 로드되더라도 초기화 되지 않고,
getInstance()가 호출될 때 인스턴스를 생성
가장 많이 쓰이는 방법
class Singleton {
private static class singleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return singleInstanceHolder.INSTANCE;
}
}
💡 5. 이중 확인 잠금
- 인스턴스 생성 여부를 싱글톤패턴 잠금 전에 한번, 객체생성 전에 또 한번 체크하여 인스턴스가 존재하지 않을 때만 잠금을 걸 수 있다.
public class Singleton {
private volatile Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
💡 volatile
- Java에서는 스레드가 2개 열리면 변수를 메인메모리가 아닌 캐시메모리에서 각각 가져오게 된다. 그러므로 같은 변수임에도 값의 불일치가 생기게 된다.
이를 해결하기 위하여 volatile
키워드를 추가하게 되면 메인메모리를 기반으로 값을 가져오기 때문에 변수값 불일치성이 해소된다.
💡 6. enum
- enum의 인스턴스는 기본적으로 스레드 세이프한 점이 보장된다.
public enum SingletonEnum {
INSTANCE;
public void getInstance() {
}
}
public class Main{
public static void main(String[] args){
SingletonEnum singleton = SingletonEnum.INSTANCE;
}
}