반응형
SingleTon
객체가 많아지면 리소스 사용이 많아지고, 리소스가 많으면 프로그램 전체 속도가 떨어진다.
-> 생성할 수 있는 객체의 최대 개수를 제한할 필요가 생김
Singleton 패턴: 객체 생성을 단 한번만 허용하는 패턴.
- 객체의 생성개수 제한이라는 조건이, 객체를 활용하려는 쪽에서 일일이 신경쓰지 않아도 되도록 하는 게 핵심.
용례
- DB Connection Pool
- Log Writer
...
@Getter
public class Database {
private static Database singleton;
private String name;
// getInstance()로 객체 불러오기.
// 객체가 없을 경우 singleton 변수에 새 인스턴스를 할당하고,
// 있으면 해당 객체를 그대로 리턴한다.
public synchronized static Database getInstance(String name) {
// 여러 쓰레드가 한 번에 접근할 때 - 순차적인 접근을 위해 synchronized 키워드 사용.
if (singleton == null) singleton = new Database(name);
return singleton;
}
// 생성자를 외부에서 호출할 수 없도록 설정.
private Database(Strign name){
try {
Thread.sleep(1000);
this.name = name;
} catch(Exception e){}
}
}
public class TestSingleton1 {
public static void main(String[] args) {
Database database;
database = Database.getInstance("1");
System.out.println(database.getName()); // return 1
database = Database.getInstance("2");
System.out.println(database.getName()); // return 1
}
}
public class TestSingleton2 {
static int nNum = 0;
public static void main(String[] args) {
Runnable task = () -> {
try {
nNum ++;
Database database = Database.getInstance(nNum + "번째 DB");
System.out.println(database.getName());
} catch (Exception e) {}
};
for (int i = 0; i<10; i++) {
Thread t = new Thread(task);
t.start();
}
/*
이렇게 되면, "1 ~ 10번째 DB" 중 여러 개가 랜덤 생성된다. 거의 10개 다 생성되지만, 10개가 아닐 수도 있음.
즉 싱글톤 패턴이지만, 각 쓰레드마다 전부 새 객체를 생성한 것.
여러 쓰레드가 동시에 getInstance()를 요청 -> 요청 시점에서 singleton객체는 null이기 때문.
getInstance() 메소드에 synchronized 키워드를 붙일 경우, 여러 쓰레드가 메소드를 동시에 호출해도 한 번에 하나씩만 처리됨. 따라서 한 번 생성된 싱글톤 객체가 계속 리턴된다.
*/
}
}
단, synchronized 키워드는 비싸다. 메소드를 요청한 쓰레드를 전부 일렬로 세우기 때문에, 병목현상이 쉽게 발생하는 지점.
또한 singleton == null을 확인하는 부분은, 프로그램 전체를 봤을 때 한 번만 실행되기만 하면 된다.
메소드 호출 시마다 검사하는 건 비효율적임
따라서, 아래처럼 수정되는 게 보다 적합하다.
@Getter
public class Database {
// static 특성을 활용해서, 처음 static으로 변수 할당할 때 객체를 생성.
private static Database singleton = new Database("init");
private String name;
// getInstance()로 객체 불러오기.
// static으로 객체를 생성한 채 시작하므로, null 비교할 필요가 없다
public static Database getInstance(String name) {
return singleton;
}
// 생성자를 외부에서 호출할 수 없도록 설정.
private Database(Strign name){
try {
Thread.sleep(1000);
this.name = name;
} catch(Exception e){}
}
}
Log Writer
자바에서는 싱글톤을 직접 사용할 일이 많지 않음. 프레임워크 내부에서 이미 적용된 채 사용되는 경우가 많기 때문
public class LogWriter {
// static 변수를 활용해 객체 정의 + 생성.
private static LogWriter singleton = new LogWriter();
// bufferedWriter로 로그 기록
private static BufferedWriter bw;
// private 생성자
private LogWriter() {
try {
// bufferedWriter 생성
bw = new BufferedWriter(new FileWriter("log.txt"));
} catch (Exception e) {}
}
// getInstance
public static LogWriter getInstance(){ return singleton; }
// writer가 정상적으로 closed될 수 있도록
@Override
protected void finalize() {
try {
super.finalize();
bw.close();
} catch (Throwable ex) {}
}
// 로그 작성 메소드
// 여러 쓰레드가 요청하더라도 하나의 파일에 작업해야 하므로 synchronized 예약어 사용
public synchronized void log(String str) {
try {
// 현재날짜 / 시간
// bw.write(LocalDateTime.now() + ":" + str + "\n");
bw.write(str + "\n");
bw.flush();
} catch (Exception e)
}
}
// 클래스 테스트용
public class TestLogWriter {
public static void main(String[] args) {
// 하나의 메인쓰레드에서 정상 작동하는지 확인.
LogWriter logger;
logger = LogWriter.getInstance();
logger.log("test1");
logger = LogWriter.getInstance();
logger.log("test2");
// 여러 쓰레드 생성 - 호출
for (int i = 0; i < 50; i++) {
Thread t = new ThreadSub();
t.start();
// 순서는 보장하지 않아도, 중복 없이 txt파일에 입력된 것을 확인할 수 있음.
}
}
}
public class ThreadSub extends Thread {
int num;
public ThreadSub(int num){ this.num = num}
@Override
public void run() {
LogWriter logger = LogWriter.getInstance();
if (num < 10) logger.log("*** 0" + num + " ***");
else logger.log("***" + num + " ***");
}
}
반응형
'학습일지 > Language' 카테고리의 다른 글
[Design Pattern] Builder (0) | 2020.12.16 |
---|---|
[Design Pattern] FlyWeight (0) | 2020.12.15 |
Spring AOP 요약 (0) | 2020.12.07 |
Java8 Stream - Optional - Date 정리 (0) | 2020.10.28 |
Java Reflection 정리 (0) | 2020.10.26 |