옵저버 패턴(Observer Pattern)이란?
"상태가 바뀌면, 알아서 알림 받는 구조"객체 간 1:N 관계를 정의하여, 하나의 객체 상태가 변경되면그 상태에 의존하는 다른 객체들에게 자동으로 알림이 가도록 한다.
예시로 쉽게 이해하기
고객 1 : "상품이 들어오면 알려줘!"마트 1 : "상품 들어오면 알려줄 테니 명단 적어줘"→ 상품이 들어오면? → 고객 1에게 자동 알림이 전송됨
이처럼 고객은 상태를 감지하지 않고,
마트가 상태 변화를 감지해서 자동으로 알려주는 구조가 옵저버 패턴이다.
🔴 Polling 방식
Polling(폴링) 은 어떤 대상(서버, 객체, 장치 등)의 상태가 변경되었는지 계속 확인하는 방식이다.
“변했니? 아직도 아니야? 지금은?”이렇게 계속 물어보는 구조를 의미한다.
1. 코드 예시 기반 설명
while (true) {
Thread.sleep(100); // 잠깐 쉬고
if (lotteMart.getValue() != null) {
customer1.update(lotteMart.getValue() + "가 들어왔습니다.");
break;
} else {
System.out.println("아직 안 들어왔어요");
}
}
이 부분이 바로 Polling이다.
0.1마다
lotteMart.getValue()
를 호출하면서 "상품 들어왔니?" 라고 계속 확인하고 있다.2. Polling의 코드를 작성 할 시 주의할 점
1. Polling 주기 설정은 비즈니스와 성능의 균형이 중요
- 짧게 설정하면 반응 속도는 빨라지지만 서버/CPU에 부하가 크다.
- 길게 설정하면 부하는 적지만 사용자 체감 반응 속도가 느려질 수 있다.
- ⇒
Thread.sleep()
또는 주기 설정 시 비즈니스 민감도 + 시스템 성능 고려 필요.
예: 채팅방 새 메시지는 1초마다, 택배 배송 상태는 10분마다
2. 스레드 수 관리 (특히 무한 루프일 때)
- 폴링을 위해 새로운 스레드를 만들고 무한 루프 돌릴 때는 스레드 자원 낭비 주의
- 자바에선
ExecutorService
같은 스레드 풀을 활용해서 스레드 개수 제한 필요
- ⇒
new Thread(...).start();
남발은 피하고, 스케줄러나 큐 기반 처리 권장
3. 조건이 충족되지 않을 때 반드시 탈출 조건 고려
- 무한
while (true)
루프 안에서break
못 하면 프로그램이 끝나지 않고 계속 돌아간다
- ⇒ 일정 횟수 이상 확인했는데도 안되면
timeout
처리해야 함
int retry = 0;
int MAX_RETRY = 100; // 10초 동안 확인
while (retry < MAX_RETRY) {
...
retry++;
}
System.out.println("상품이 들어오지 않았습니다. 종료합니다.");
4. 불필요한 리소스 점유 최소화
while (true)
안에Thread.sleep()
이 없으면 CPU가 쉬지 않고 루프를 계속 돌게 된다.
- 이걸 Busy Waiting이라고 하며, CPU 자원을 100% 가까이 점유하게 된다. 100% 를 점유하게 되면 안되는 이유점은 아래와 같다.
- 👉 문제점:
- 해당 스레드는 계속 실행 중이라 CPU 코어를 독점함
- 그 결과, 다른 스레드나 작업들이 CPU를 사용할 기회를 얻지 못함
- 이는 전체 시스템 성능을 심각하게 저하시킬 수 있음
실시간 반응성이 필요한 경우에도 짧은 주기로 sleep을 반복하며 체크하는 게 낫고,sleep 없이 무한루프
는 절대 지양해야 함.
5. 네트워크/DB polling 시에는 더 주의
- DB나 서버에 주기적으로 요청을 보내는 polling은 부하가 훨씬 큼
- 이때는 polling 대신
WebSocket
,SSE
,Webhook
같은 이벤트 기반 방식이 더 나을 수 있음
🟠 Push 방식
Push는 대상(서버, 객체 등)이 상태가 바뀌었을 때 스스로 알림을 보내주는 방식이다.
“필요하면 내가 알려줄게!”Polling은 계속 물어보는 구조였다면, Push는 상태가 바뀌었을 때 알아서 알려주는 구조다.
1. 코드 예시 기반 설명
public class LotteMart implements Mart {
// 구독자 명단
private List<Customer> customerList = new ArrayList<>();
// 구독 등록
@Override
public void add(Customer customer) {
customerList.add(customer);
}
// 구독 취소
@Override
public void remove(Customer customer) {
customerList.remove(customer);
}
@Override
public void received() {
for (int i = 0; i < 5; i++) {
System.out.println(".");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
notify("lotteMart : 바나나");
}
@Override
public void notify(String msg) {
for (Customer customer : customerList) {
customer.update(msg);
}
}
}
LotteMart
가 상품이 들어오면customer.update()
를 직접 호출해서 알림을 발송한다.
- 고객은 기다리고만 있어도 되고, 마트가 알아서 알려준다
2. Push의 특징과 장점
✅ 1. 불필요한 반복 요청이 없음
- Polling처럼 “들어왔어?”, “지금은?”, “이제는?” 이런 반복 요청이 없음
- 네트워크, CPU, 메모리 리소스 낭비가 없다
✅ 2. 이벤트 기반이라 반응이 즉각적임
- 변화가 생기자마자 바로 반응 가능
- ex) 실시간 알림, 실시간 채팅, 실시간 주식 가격
✅ 3. 옵저버 패턴과 잘 맞음
- 옵저버(Observer)들이 등록만 해두면,
- 주제(Subject)가 알아서 상태 변경 시 알림을 푸시해줌
- → 느슨한 결합 구조도 만들어줌
3. Push 구현시 주의할 점
⚠️ 1. 관찰자(Observer) 관리 필요
- 상태가 바뀔 때 푸시할 대상(옵저버 리스트)을 잘 관리해야 함
- 누락되면 알림 못 받음, 중복되면 여러 번 알림
⚠️ 2. 비동기 처리 고려
- 여러 observer에게 동시에 알림 보낼 땐 비동기 처리 고려
- 한 observer가 느려도 전체 푸시가 지연될 수 있음
⚠️ 3. Push 실패 대응 필요
- 네트워크 오류나 예외 발생 시, 푸시 실패할 수 있음
- → 실패 시 재시도 로직, 큐, 로그 기록 등을 고려해야 함
🟡 두 방식 비교 정리
Polling과 Push는 모두 외부 상태의 변화를 감지하는 방법이다.
하지만 "누가 주도해서 상태를 확인하는가"에 따라 다음과 같은 차이가 있다:
- Polling : "내가 계속 물어봐야 함"
- Push (옵저버 패턴) : "상태가 바뀌면 알려줌"
실시간성, 리소스 효율 면에서는 Push 방식(옵저버 패턴)이 훨씬 유리하다.다만 구현 복잡도나 네트워크 환경에 따라 Polling을 선택하는 경우도 있다.
Share article