[ 14주차 - 1120 ] - 오후
금일 커리큘럼
├ 09:00 ~ 12:00 Devops (other posting...)
└ 13:00 ~ 18:00 Java (GoF 디자인 패턴 개념, 싱글턴 패턴)
GoF 디자인 패턴
GoF(Gang of Four)가 제안한 23가지 디자인 패턴을 의미
디자인 패턴의 가치
- 재사용성 : 이미 검증된 설계 패턴을 사용하여 코드의 재사용성을 높임
- 유지보수성 : 코드의 구조를 명확히 하여 유지보수를 용이하게 함
- 유연성 : 변화하는 요구사항에 대응하기 쉬운 설계를 가능하게 함
- 커뮤니케이션 : 개발자들 간에 공통된 용어와 개념을 제공하여 의사소통을 원활하게 함
- 객체지향 극대화 : 낮은 결합도와 높은 응집도를 가진 객체지향 설계를 촉진함
디자인 패턴의 분류
- 생성 패턴(Creational) : 객체 생성과 관련된 패턴
- 구조적 패턴(Structural) : 클래스와 객체의 조합과 관련된 패턴
- 행위 패턴(Behavioral) : 객체 간의 상호작용과 책임 분배와 관련된 패턴
생성 패턴의 종류 ( 5 )
- 싱글턴 패턴(Singleton) : 클래스의 인스턴스가 하나만 생성되도록 보장하는 패턴
- 팩토리 패턴(Factory) : 객체 생성 로직을 캡슐화하여 클라이언트 코드와 분리하는 패턴
- 추상 팩토리 패턴(Abstract Factory) : 관련된 객체들의 군을 생성하는 인터페이스를 제공하는 패턴
- 빌더 패턴(Builder) : 복잡한 객체의 생성 과정을 단계별로 분리하여 객체를 생성하는 패턴
- 프로토타입 패턴(Prototype) : 기존 객체를 복제하여 새로운 객체를 생성하는 패턴
구조적 패턴의 종류 ( 7 )
- 어댑터 패턴(Adapter) : 서로 호환되지 않는 인터페이스를 가진 클래스들을 연결하는 패턴
- 브리지 패턴(Bridge) : 구현부에서 추상층을 분리하여 독립적으로 변형할 수 있게 하는 패턴
- 컴포지트 패턴(Composite) : 객체들을 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴
- 데코레이터 패턴(Decorator) : 객체에 추가적인 기능을 동적으로 부여하는 패턴
- 퍼사드 패턴(Facade) : 복잡한 서브시스템에 대한 단순화된 인터페이스를 제공하는 패턴
- 플라이웨이트 패턴(Flyweight) : 많은 수의 유사한 객체들을 효율적으로 공유하는 패턴
- 프록시 패턴(Proxy) : 다른 객체에 대한 접근을 제어하는 대리자 객체를 제공하는 패턴
행위 패턴의 종류 ( 11 )
- 책임 연쇄 패턴(Chain of Responsibility) : 요청을 처리할 객체를 연결하여 요청을 전달하는 패턴
- 커맨드 패턴(Command) : 요청을 객체로 캡슐화하여 다양한 요청을 처리하는 패턴
- 인터프리터 패턴(Interpreter) : 언어의 문법을 표현하는 클래스들을 구성하는 패턴
- 이터레이터 패턴(Iterator) : 컬렉션의 요소들에 접근하는 방법을 제공하는 패턴
- 중재자 패턴(Mediator) : 객체들 간의 상호작용을 중재하는 객체를 제공하는 패턴
- 메멘토 패턴(Memento) : 객체의 상태를 저장하고 복원하는 패턴
- 옵저버 패턴(Observer) : 객체의 상태 변화에 따라 의
- 상태 패턴(State) : 객체의 상태에 따라 행동을 변경하는 패턴
- 전략 패턴(Strategy) : 알고리즘을 캡슐화하여 교환 가능하게 하는 패턴
- 템플릿 메서드 패턴(Template Method) : 알고리즘의 구조를 정의하고 일부 단계를 서브클래스에서 구현하도록 하는 패턴
- 비지터 패턴(Visitor) : 객체 구조에 새로운 연산을 추가하는 패턴
스프링부트에서 자주쓰이던 패턴
생성 패턴
- 싱글턴 패턴 : 스프링의 기본 Bean 스코프가 싱글턴
- bean 객체가 애플리케이션 당 하나만 생성되어 공유되는 개념만 동일하고,
- 구현 방식은 일반 싱글턴과 다름.
- 팩토리 패턴 : 스프링의 BeanFactory, FactoryBean
- 빌더 패턴 : Lombok
@Builder, DTO/엔티티 생성 시 사용
구조적 패턴
- 어댑터 패턴 : 스프링 MVC의 HandlerAdapter
- 데코레이터 패턴 : Spring Security 필터 체인, HttpMessageConverter 래핑
- 퍼사드 패턴 : 스프링 MVC에서 Service 계층이 퍼사드 역할
- 컨트롤러가 서비스 계층에 단순화된 인터페이스로 접근하도록 되어있고,
- 서비스 계층이 내부적으로 복잡한 비즈니스 로직을 처리하기 때문임
- 즉, 컨트롤러는 접근만 <-> 서비스는 로직처리만
- 프록시 패턴 : AOP,
@Transactional, CGLIB/JDK 동적 프록시 기반
행위 패턴
- 책임 연쇄 패턴 : Spring Security 필터 체인
- 커맨드 패턴 :
@RequestMapping→ 요청을 메서드 호출로 연결 - 옵저버 패턴 : ApplicationEventPublisher,
@EventListener - 전략 패턴 : 인터페이스 기반 빈 교환 구조 (DI 핵심)
- 템플릿 메서드 패턴 : JdbcTemplate, RestTemplate, AbstractController
2. Singleton Pattern
- 클래스의 인스턴스가 하나만 생성되도록 보장하는 디자인 패턴
- 전역 접근점을 제공하여 어디서든 동일한 인스턴스에 접근 가능
싱글턴 패턴이 필요한 경우
- 1) 애플리케이션 전체에서 공유되는 리소스 관리 (예: 설정 정보, 로깅, DB 연결 등)
// DB 연결 관리 예시
// Config가 일반 클래스일 때 -----------------
// 잘못된 예 ❌ : DB 연결 객체가 여러 개 생성될 수 있음
Config config1 = new Config();
Config config2 = new Config();
// Config가 싱글턴 패턴 클래스일 때 -----------------
// 올바른 예 ✅ : 싱글턴 패턴을 사용하여 하나의 인스턴스만으로 사용
Config config = Config.getInstance();
Config config2 = Config.getInstance();
// config1 == config2는 동일한 인스턴스
- 2) 로그 관리 (예: 로그를 기록하는 Logger)
// Logger가 일반 클래스일 때 -----------------
// 잘못된 예 ❌ : Logger 객체가 여러 개 생성될 수 있음
Logger logger1 = new Logger();
Logger logger2 = new Logger();
// Logger가 싱글턴 패턴 클래스일 때 -----------------
// 올바른 예 ✅ : 싱글턴 패턴을 사용하여 하나의 인스턴스만으로 사용
Logger logger1 = Logger.getInstance();
Logger logger2 = Logger.getInstance();
// logger1 == logger2는 동일한 인스턴스
싱글턴 구현 - 즉시 초기화 방식
- 클래스 로딩 시점에 인스턴스를 미리 생성하여 제공
- ✅ 장점 : 구현 간단, 멀티스레드 환경에서 안전, 동기화 비용이 없음
- ❌ 단점 : 인스턴스가 필요하지 않아도 메모리를 차지할 수 있음
public class Singleton {
// 1. 클래스 로딩 시점에 인스턴스 생성
private static final Singleton instance = new Singleton();
// 2. private 생성으로 외부 생성 차단
private Singleton() {
// 초기화 코드
}
// 3. 전역 접근점을 제공하는 메서드
public static Singleton getInstance() {
return instance; // 같은 인스턴스 반환해줌
}
}
싱글턴 구현 - 지연 초기화 방식
- 인스턴스가 처음 필요할 때 생성하는 방식임
- ✅ 장점 : 인스턴스가 필요할 때만 생성되어 메모리 절약 가능
- ❌ 단점 : 멀티스레드 환경에서 동기화 문제 발생 가능, 구현이 복잡해짐
- 여러 스레드가 동시에 getInstance() 호출 시 여러 인스턴스가 생성될 수 있기 때문
public class Singleton {
// 1. 인스턴스를 저장할 private static 변수
private static Singleton instance;
// 2. private 생성으로 외부 생성 차단
private Singleton() {
// 초기화 코드
}
// 3. 전역 접근점을 제공하는 메서드
public static synchronized Singleton getInstance() {
// 4. 인스턴스가 없을 때만 생성
if (instance == null) {
instance = new Singleton();
}
return instance; // 같은 인스턴스 반환해줌
}
}
싱글턴 구현 - 이중 검증 잠금 방식
- 지연 초기화 방식의 단점을 보완한 방법
- ✅ 장점 : 멀티스레드 환경에서 안전하면서도 동기화 비용을 줄임
- ❌ 단점 : 구현이 다소 복잡함
public class Singleton {
// 1. 인스턴스를 저장할 private static 변수
// volatile : 멀티스레드 환경에서 인스턴스 변수의 가시성을 보장
private static volatile Singleton instance;
// 2. private 생성으로 외부 생성 차단
private Singleton() {
// 초기화 코드
}
// 3. 전역 접근점을 제공하는 메서드
public static Singleton getInstance() {
// 4. 첫 번째 검증 (인스턴스가 없을 때만 동기화)
if (instance == null) {
synchronized (Singleton.class) {
// 5. 두 번째 검증 (동기화된 블록 내에서 다시 확인)
if (instance == null) {
instance = new Singleton();
}
}
}
return instance; // 같은 인스턴스 반환해줌
}
}
- volatile 키워드는 멀티스레드 환경에서 변수의 가시성을 보장
- volatile는 변수에 대한 읽기/쓰기가 메인 메모리에서 직접 이루어지도록 하여,
- 여러 스레드가 동시에 접근할 때 최신 값을 보장함
- synchronized 블록을 사용하여 동기화 처리
- synchronized = 한번에 하나의 메서드만
싱글턴 구현 - Static Inner Class 방식 (권장함)
- 내부클래스로 선언하여 인스턴스를 생성하는 방식
- ✅ 장점 : 멀티스레드 환경에서 안전, 지연 초기화 가능, 구현이 간단
- ❌ 단점 : 특별한 단점 없음
public class Singleton {
// 1. private 생성으로 외부 생성 차단
private Singleton() {
// 초기화 코드
}
// 2. static inner class를 이용한 인스턴스 생성
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 3. 전역 접근점을 제공하는 메서드
public static Singleton getInstance() {
return SingletonHolder.INSTANCE; // 같은 인스턴스 반환해줌
}
}
- 내부 클래스로 선언된
SingletonHolder클래스는 Singleton클래스가 로드될 때는 로드되지 않고,getInstance()메서드가 처음 호출될 때 로드되어 인스턴스를 생성함- 이러한 방법으로 지연 초기화와 멀티스레드 안전성을 동시에 보장함
etc. 싱글턴에 대해 짚고넘어가기
스프링에서는 ?
- 스프링 프레임워크는 기본적으로 빈(Bean)들을 싱글턴으로 관리
- 스프링 컨테이너가 애플리케이션 당 하나의 빈 인스턴스를 생성하고 공유
- 개발자가 구현하는 방식과 스프링 Bean의 싱글턴 방식 자체가 다름
다른이유 ?
- 스프링 싱글턴은 스프링 컨테이너가 관리하는 범위 내에서의 싱글턴임
- 즉, 스프링 컨테이너가 여러 개일 경우 각 컨테이너마다 별도의 싱글턴 인스턴스가 존재할 수 있음
- 반면, 전통적인 싱글턴 패턴은 JVM 전체에서 하나의 인스턴스를 보장함
싱글턴 패턴 큰 단점
1. 테스트 어려움
- 싱글턴 인스턴스는 전역 상태를 가지므로, 테스트 간에 상태가 공유될 수 있음
- 이는 테스트의 독립성을 해칠 수 있으며, 테스트 간의 상호작용을 유발함
- 싱글턴 인스턴스를 초기화하거나 재설정하는 방법을 제공하여 테스트 환경을 제어해야 함
2. 전역 상태 문제
- 싱글턴 인스턴스가 전역 상태를 가지므로 어디든 접근가능 (숨겨진 의존성 발생)
- 객체 지향 설계 원칙 중 하나인 의존성 주입(DI) 원칙을 위반할 수 있음
3. 멀티스레드 환경 문제
- 스레드 안전성을 보장하지 않는 싱글턴 구현은 멀티스레드 환경에서 문제가 발생할 수 있음
- 적절한 동기화 메커니즘을 사용하여 스레드 안전성을 확보해야 함
4. 확장성 불가
- 만약 인스턴스가 여러 개 필요해지면 싱글턴 패턴은 적합하지 않음
- 예를 들어, 데이터베이스 연결 풀과 같이 여러 인스턴스가 필요한 경우에는 쓰기 힘듬
실무에서는 어떨때 쓰일까 ?
1. 설정 관리 (Configuration)
- 애플리케이션 설정 정보를 중앙에서 관리
AppConfig — 설정 파일자동 로드 + 파일변경 감지
- 용도 : 설정 파일을 로드하고, 변경 사항을 감지하여 자동으로 재로드하는 기능 제공
- 특징 : WatchService를 사용하여 파일 변경 감지, Properties 객체로 설정 관리
import java.io.IOException;
import java.nio.file.*;
import java.util.Properties;
/**
* 애플리케이션 설정을 관리하는 싱글턴
* - 설정 파일(config.properties)을 로드
* - 파일 변경을 감지하여 자동으로 리로드(핫 리로드)
*/
public class AppConfig {
private final Properties props = new Properties();
private AppConfig() {
load();
watchConfigFile();
}
private static class Holder {
private static final AppConfig INSTANCE = new AppConfig();
}
public static AppConfig getInstance() {
return Holder.INSTANCE;
}
/**
* 설정 파일 로드
* config.properties 파일을 읽어서 props에 저장
*/
private void load() {
try {
props.load(Files.newInputStream(Paths.get("config.properties")));
System.out.println("[Config] 설정 로드 완료.");
} catch (IOException e) {
throw new RuntimeException("설정 파일 로드 실패", e);
}
}
/**
* 설정 파일 변경 감지
* - WatchService 로 특정 파일이 변경되면 load() 호출
* - 백그라운드 스레드에서 감시
*/
private void watchConfigFile() {
Thread watcher = new Thread(() -> {
try {
WatchService watchService = FileSystems.getDefault().newWatchService();
Paths.get(".").register(
watchService,
StandardWatchEventKinds.ENTRY_MODIFY
);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
Path changed = (Path) event.context();
if (changed.getFileName().toString().equals("config.properties")) {
System.out.println("[Config] 변경 감지 → 재로드");
load();
}
}
key.reset();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
watcher.setDaemon(true); // 데몬 스레드 → JVM 종료 시 자동 종료
watcher.start();
}
public String get(String key) {
return props.getProperty(key);
}
}
// config.properties 파일 내용 예시 :
// db.url=jdbc:mysql://localhost:3306/newdb
// 사용 예시
AppConfig config = AppConfig.getInstance();
String dbUrl = config.get("db.url");
System.out.println("DB URL: " + dbUrl);
// 출력결과 :
// DB URL: jdbc:mysql://localhost:3306/newdb
// 이후 config.properties 파일을 수정할 경우
// 출력결과 :
// [Config] 변경 감지 → 재로드
String newDbUrl = config.get("db.url");
System.out.println("DB URL: " + newDbUrl);
// 출력결과 :
// DB URL: jdbc:mysql://localhost:3306/updateddb
2. 로깅 관리 (Logging)
- 애플리케이션 로그를 중앙에서 관리
AsyncLogger — 비동기 큐 기반 로깅 시스템
- 용도 : 로그 메시지를 비동기적으로 파일에 기록하여 성능 향상
- 특징 : BlockingQueue를 사용하여 로그 메시지 큐잉, 별도 스레드에서 파일 기록
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.*;
/**
* 비동기 로깅 싱글턴
* - 로그를 즉시 파일에 쓰지 않고 BlockingQueue에 넣어둠
* - 별도 로거 스레드가 큐에서 꺼내 파일에 기록 → 성능 향상
*/
public class AsyncLogger {
// 로그 메시지가 저장될 큐(생산자-소비자 모델)
private final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
private AsyncLogger() {
startWorker();
}
private static class Holder {
private static final AsyncLogger INSTANCE = new AsyncLogger();
}
public static AsyncLogger getInstance() {
return Holder.INSTANCE;
}
/**
* 로그를 큐에 넣는 메서드 — 즉시 리턴하므로 매우 빠름
*/
public void log(String message) {
queue.offer(message);
}
/**
* 별도 스레드가 queue.take()로 메시지를 계속 받으며 파일에 기록
*/
private void startWorker() {
Thread worker = new Thread(() -> {
try (FileWriter writer = new FileWriter("app.log", true)) {
while (true) {
// 큐가 비어있으면 블록됨 → CPU 낭비 없음
String msg = queue.take();
writer.write(msg + "\n");
writer.flush();
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("AsyncLogger 오류", e);
}
});
worker.setDaemon(true);
worker.start();
}
}
// 사용 예시
AsyncLogger logger = AsyncLogger.getInstance();
logger.log("애플리케이션 시작");
Runnable task = () -> {
logger.log("스레드 로그: " + Thread.currentThread().getName());
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
try {
int result = 10 / 0;
} catch (Exception e) {
logger.log("에러 발생: " + e.getMessage());
}
logger.log("애플리케이션 종료");
3. DB 연결 풀 (DB Connection Pool)
- 데이터베이스 연결을 효율적으로 관리하고 재사용
DBConnectionPool — 싱글턴 DB 연결 풀
- 용도 : 일정 수의 DB 커넥션을 미리 생성해두고 재사용하여 성능 향상
- 특징 : ConcurrentLinkedQueue를 사용하여 커넥션 풀 관리
- 별도 헬스체커 스레드가 커넥션 상태 점검
- 오래된 idle 커넥션 정리 스레드 포함
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.*;
import java.util.*;
/**
* Connection Pool 구현
* - 일정 수의 DB 커넥션을 미리 생성해 풀에 저장
* - 사용 요청이 오면 꺼내주고, 반납하면 다시 저장
* - 헬스 체크 스레드가 주기적으로 커넥션 상태 확인
* - idle cleaner가 오래된 커넥션 정리 후 새로 생성
*/
public class ConnectionPool {
private final int POOL_SIZE = 10; // 풀 크기 설정
private final Queue<Connection> pool = new ConcurrentLinkedQueue<>();
private final Map<Connection, Long> lastUsedTime = new ConcurrentHashMap<>();
// DB 커넥션 정보
private final String DB_URL = "jdbc:mysql://localhost:3306/mydb";
private final String DB_USER = "root";
private final String DB_PASSWORD = "비밀번호";
// idle 커넥션 정리 주기 및 커넥션 생존체크 주기
private final int IDLE_CLEANER_TIME = 5 * 60 * 1000; // 5분
private final int HEALTH_CHECK_TIME = 30 * 1000; // 30초
private ConnectionPool() {
initPool();
startHealthChecker();
startIdleCleaner();
}
private static class Holder {
private static final ConnectionPool INSTANCE = new ConnectionPool();
}
public static ConnectionPool getInstance() {
return Holder.INSTANCE;
}
/**
* 초기 풀 생성
*/
private void initPool() {
for (int i = 0; i < POOL_SIZE; i++) {
pool.add(createConnection());
}
}
/**
* 새로운 DB 커넥션 생성
*/
private Connection createConnection() {
try {
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
lastUsedTime.put(conn, System.currentTimeMillis());
return conn;
} catch (Exception e) {
throw new RuntimeException("DB 연결 실패", e);
}
}
/**
* 커넥션 가져오기
* - pool이 비어있으면 신규 생성
*/
public Connection getConnection() {
Connection conn = pool.poll();
if (conn == null) {
conn = createConnection();
}
lastUsedTime.put(conn, System.currentTimeMillis());
return conn;
}
/**
* 커넥션 반납
*/
public void returnConnection(Connection conn) {
lastUsedTime.put(conn, System.currentTimeMillis());
pool.offer(conn);
}
/**
* 커넥션 살아있는지 주기적으로 체크 (30초마다)
*/
private void startHealthChecker() {
Thread checker = new Thread(() -> {
while (true) {
try {
Thread.sleep(HEALTH_CHECK_TIME);
for (Connection conn : pool) {
if (!conn.isValid(2)) {
pool.remove(conn);
pool.offer(createConnection());
}
}
} catch (Exception e) {
System.out.println("헬스체커 오류: " + e.getMessage());
}
}
});
checker.setDaemon(true);
checker.start();
}
/**
* 오래된 idle 커넥션 제거 (5분 이상 미사용)
*/
private void startIdleCleaner() {
Thread cleaner = new Thread(() -> {
while (true) {
try {
Thread.sleep(IDLE_CLEANER_TIME);
long now = System.currentTimeMillis();
for (Connection conn : pool) {
if (now - lastUsedTime.get(conn) > IDLE_CLEANER_TIME) {
pool.remove(conn);
pool.offer(createConnection());
}
}
} catch (Exception e) {
System.out.println("IdleCleaner 오류: " + e.getMessage());
}
}
});
cleaner.setDaemon(true);
cleaner.start();
}
}
// 사용 예시
DBConnectionPool pool = DBConnectionPool.getInstance();
Connection conn = null;
ResultSet rs = null;
try {
// 쿼리 실행 등 작업 수행
conn = pool.getConnection();
rs = conn.prepareStatement("SELECT * FROM users").executeQuery();
while (rs.next()) {
System.out.println("User: " + rs.getString("username"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null) rs.close(); // ResultSet 은 닫아도 OK
} catch (Exception ex) {
ex.printStackTrace();
}
// 커넥션 반납
pool.returnConnection(conn); // close() 절대 X
// 반납 후 재사용 됨
}
4. 캐시 관리 (Caching)
- 자주 사용하는 데이터를 메모리에 저장하여 빠른 접근 제공
CacheManager — LRU 캐시 + 만료 스레드
- 용도 : LRU(Least Recently Used) 기반의 캐시 관리 + TTL(만료시간) 기능
- 특징 : LinkedHashMap을 사용하여 LRU 구현, 별도 스레드가 TTL 만료된 항목 정리
import java.util.*;
/**
* LRU + TTL(만료시간) 캐시 매니저
* - LRU 기반의 LinkedHashMap 사용
* - TTL만료 스레드가 주기적으로 오래된 캐시 삭제
*/
public class CacheManager {
private static final int MAX_SIZE = 100;
/**
* LinkedHashMap + accessOrder = true → LRU 작동
*/
private final Map<String, CacheEntry> cache =
new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, CacheEntry> eldest) {
return size() > MAX_SIZE;
}
};
private CacheManager() {
startCleaner();
}
private static class Holder {
private static final CacheManager INSTANCE = new CacheManager();
}
public static CacheManager getInstance() {
return Holder.INSTANCE;
}
/**
* 캐시 저장
*/
public synchronized void put(String key, Object value, long ttlMillis) {
cache.put(key, new CacheEntry(value, System.currentTimeMillis() + ttlMillis));
}
/**
* 캐시 조회 + 만료 체크
*/
public synchronized Object get(String key) {
CacheEntry entry = cache.get(key);
if (entry == null) return null;
if (System.currentTimeMillis() > entry.expireTime) {
cache.remove(key);
return null;
}
return entry.value;
}
/**
* TTL이 지난 데이터를 삭제하는 스레드
* - 10초마다 캐시 스캔 후 만료된 항목 제거
*/
private void startCleaner() {
Thread cleaner = new Thread(() -> {
while (true) {
try {
Thread.sleep(10000);
long now = System.currentTimeMillis();
Iterator<Map.Entry<String, CacheEntry>> it = cache.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, CacheEntry> entry = it.next();
if (now > entry.getValue().expireTime) {
it.remove();
}
}
} catch (Exception e) {
System.out.println("캐시 클리너 오류: " + e.getMessage());
}
}
});
cleaner.setDaemon(true);
cleaner.start();
}
/** 캐시 엔트리 구조 */
static class CacheEntry {
Object value;
long expireTime;
CacheEntry(Object value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
}
}
CacheManager cache = CacheManager.getInstance();
// 캐시 저장 (키, 값, TTL 5분)
cache.put("user_123", userObject, 5 * 60 * 1000);
// 캐시 조회
Object user = cache.get("user_123");
if (user == null) {
// 캐시 미스 or 만료
}
// 캐시 갱신
cache.put("user_123", updatedUserObject, 5 * 60 * 1000);
// 캐시 삭제 (즉시 삭제되지는 않음, 10초 후 Cleaner가 지움)
cache.remove("user_123");
싱글턴 핵심 요약
- 패턴 의미 : 클래스의 인스턴스를 단 하나만 생성하고 전역적으로 접근
- 구현 방법 : Eager, Lazy, Double-Checked Locking, Static Inner Class
- 권장 방식 : Static Inner Class (성능, 안전성, 코드 가독성 우수)
- 적용 사례 : 설정 관리, 로거, DB 연결 풀, 캐시
- 주의 사항 : 테스트 어려움, 전역 상태 문제, 과도한 사용 지양
'멋사 - 부트캠프 19기 : Java > Java' 카테고리의 다른 글
| (10.02) Java 프로그래밍 - UDP 통신, UDP Echo, 고급 네트워크, HTTP , JFrame (0) | 2025.10.02 |
|---|---|
| (10.01) Java 프로그래밍 - Network 개념, TCP 통신 이해, Java 네트워크 프로그래밍, Socket, TCP 채팅 (0) | 2025.10.01 |
| (09.30) Java 프로그래밍 - Stream API, 스트림 생성, 스트림 연산, 옵셔널 (0) | 2025.09.30 |
| (09.29) Java 프로그래밍 - 스레드 통신, 제어, 데드락 해결방식, 함수형 프로그래밍 (0) | 2025.09.29 |
| (09.25 ②) Java 프로그래밍 - 프로세스와 스레드, 스레드 생성, 동기화, 데몬스레드, 스레드 제어 메서드 (0) | 2025.09.25 |