Singleyon pattern
1. 싱글톤 log mannager 문제
- public static synchronized LogManager getInstance() 이런식으로 멀티스레드때의 위험을 피할 수 있다.
public class LogManagerMain {
public static void main(String[] args) {
ModuleA a = new ModuleA();
ModuleB b = new ModuleB();
ModuleC c = new ModuleC();
a.run();
b.run();
c.run();
}
}
class LogManager{
private static LogManager logManager;
private LogManager(){}
public static LogManager getInstance(){
if (logManager == null){
logManager = new LogManager();
System.out.println("logmanager 객체가 생성되었습니다.");
}
return logManager;
}
public void log(String message){
System.out.printf("[log] Module%s 실행\n", message);
}
}
interface Module {
public void run();
}
class ModuleA implements Module{
public void run(){
LogManager.getInstance().log("A");
}
}
class ModuleB implements Module{
public void run(){
LogManager.getInstance().log("B");
}
}
class ModuleC implements Module{
public void run(){
LogManager.getInstance().log("C");
}
}
개선
- 멀티 스레드 환경에서 LogManager의 초기화와 getInstance의 초기화 시점이 엇갈리면서 get매서드가 클래스 초기화시에 하나의 스레드만 처리할 수 있게 도와주는 락의 이득을 못봄. 하여 내부정적 클래스를 만듦으로 내부 클래스의 초기화와 get매서드의 초기화가 같이 일어날 수 있게 함으로서 하나의 스레드만 안전하게 통과시킬 수 있음.
- 내부 클래스는 외부 클래스 초기화시 정의(내가 있다!, 메모리 할당할 준비해라)만 되고 초기화는 되지 않아 사용시점에 같이 초기화 시킬 수 있음.
- final은 변수가 한번만 초기화되는 것을 보장함. 다른 객체로 대체되지 않음.
- static이 아닌 인스턴스 매서드는 객체의 고유한 상태에 따라 다르게 동작할 수 있도록 설계되어 객체없이는 사용할 수 없다. 즉 누군가가 해야한다고 설계도에 적어둔 느낌이다. static은 설계도와 함께 존재하며 설계도를 들여다보면 보이는 당연한 사실 같은 것이다.
- 자동차 설계도가 주행(인스턴스 매서드드)을 할 수 있는건 아니지만 설계도에 적혀있는 비밀(static 매서드드)은 들여다보면 누구든 알 수 있다.
class LogManager{
private static class Holder{
private static final LogManager logManager = new LogManager();
}
private LogManager(){}
public static LogManager getInstance(){
return Holder.logManager;
}
public void log(String message){
System.out.printf("[log] Module%s 실행\n", message);
}
}
interface Module {
public void run();
}
class ModuleA implements Module{
public void run(){
LogManager.getInstance().log("A");
}
}
class ModuleB implements Module{
public void run(){
LogManager.getInstance().log("B");
}
}
class ModuleC implements Module{
public void run(){
LogManager.getInstance().log("C");
}
}
2. 싱글톤 심화: 서버 연결 상태 관리 시스템
- start()는 내부적으로 OS에 "새로운 스레드 시작해줘!"라고 요청하는 구조임
- Thread는 실제로 작업을 실행해줄 실행기(executor)이고, Runnable은 실행할 작업(job)을 담고 있는 설계도이다다.
package com.design_pattern_trainning;
public class Main {
public static void main(String[] args) {
Thread a = new Thread(new ModuleX());
Thread b = new Thread(new ModuleY());
Thread c = new Thread(new ModuleZ());
a.start();
b.start();
c.start();
}
}
class ConnectionManager{
private ConnectionManager(){}
private static boolean connected = false;
private static class Holder{
private static final ConnectionManager connection = new ConnectionManager();
}
public static ConnectionManager getInstance(){
return Holder.connection;
}
public synchronized void connect(){
if (connected){
System.out.println("이미 연결되어 있습니다.");
}
else {
System.out.println("서버에 연결합니다.");
connected = true;
}
}
public synchronized void disconnect(){
if (connected){
System.out.println("서버연결을 종료합니다.");
connected = false;
}
else {
System.out.println("이미 연결이 해제된 상태입니다.");
}
}
public void getStatus(){
String status = connected ? "연결됨." : "연결되어있지 않음.";
System.out.println(Thread.currentThread().getName() + ": 현재 상태 → " + status);
}
}
class ModuleX implements Runnable{
public void run(){
ConnectionManager manager = ConnectionManager.getInstance();
manager.getStatus();
manager.connect();
}
}
class ModuleY implements Runnable{
public void run(){
ConnectionManager manager = ConnectionManager.getInstance();
manager.getStatus();
manager.connect();
}
}
class ModuleZ implements Runnable{
public void run(){
ConnectionManager manager = ConnectionManager.getInstance();
manager.getStatus();
manager.disconnect();
}
}