[JAVA] 62. 옵저버 패턴 (관찰자 패턴)

윤설안's avatar
Jul 23, 2025
[JAVA] 62. 옵저버 패턴 (관찰자 패턴)

옵저버 패턴(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

An's Blog