2015년 9월 10일 목요일

[Java] Poison Pill Shutdown

Java Concurrency Framework을 사용해서 Producer/Consumer 형태의 코드를 종종 작성하게 됩니다. 대표적으로 사용하는 것이 바로 BlockingQueue 입니다. BlockingQueue는 thread-safety하게 작성되었기 때문에, thread 처리에 대한 고민을 덜어줍니다.

BlockingQueue에 객체를 넣는 역할을 Producer(생산자)라고 하고,
BlockingQueue에서 객체를 빼내와서 어떤 작업을 하는 것을 Consumer(소비자)라고 합니다.
전형적인 Producer/Consumer 코드는 아래와 같은 모습을 합니다.

public class Producer {
    BlockingQueue<Object> queue = new LinkedBlockingQueue<>()

    public void produce(Object o) {
        queue.add(o);
    }
}

public class Consumer implements Runnable {
    private BlockingQueue<Object> queue;
    public Consumer(BlockingQueue<Object> queue) {
        this.queue = queue;
    }

    public void run() {
       try {
            while(true) {
                Object o = queue.take()
                // do something
            }
       }
       catch() {
            // do somehting
       }
    }
}
여기서 문제가 될 수 있는 부분은 바로 queue.take() 입니다.
take() 메쏘드는 queue가 비어있는 경우 block이 됩니다.
queue에 무엇인가 있지 않으면 영원히 종료되지 않을 수 있다는 뜻이기도 합니다.

BlockingQueue에는 poll이라는 메쏘드도 있어서, queue가 비어 있는지를 확인하고 비어 있으면, 일정 시간 동안만 blocking 하고 있다가, 다음 실행을 할 수도 있습니다.
 그런데 queue의 값을 계속 처리해야하는 Consumer의 경우는
- queue가 비어 있는지를 확인, 
- 비어 있으면 정해지 시간동안 기다림
- 아래 문장을 실행
- 다시 queue에 값이 있는지 확인 
이런 일을 반복해야 합니다.

이런 경우를 busy-wait라고 합니다. queue의 값이 오기를 기다리기는 하는데 계속 분주하게 움직이고 있는 것이지요.
그래서 저 같은 경우는 take() 메쏘드를 선호합니다. 

그러면 take() 메쏘드를 사용하면서도 영원히 종료되지 않는 문제를 해결하면서 graceful shutdown을 하려면 어떻게 해야 할까요?
이 때 사용하는 것이 Poison Pill(독약) 입니다. 독약을 먹여서 thread를 죽이는 것이지요.

Poison Pill은 무한 루프를 벗어나기 위한 조건이 되는 특별한 객체를 의미합니다. 
시스템이 shutdown 한다는 신호를 받으면, Poison Pill 객체를 queue에 넣고, Consumer는 Poison Pill 객체를 받으면 무한 루프를 벗어나는 것입니다.
public class Consumer implements Runnable {
    private BlockingQueue<&gt queue;
    public Consumer(BlockingQueue<Object> queue) {
        this.queue = queue;
    }

    public void run() {
       try {
            while(true) {
                Object o = queue.take()
                if(o instanceof PoisonPill) {
                     break;
                }
                // do something
            }
       }
       catch() {
            // do somehting
       }
    }

    private class PoisonPill {
    }
}
클래스 이름은 원하는 것으로 아무거나 만들어도 됩니다.

 사실 별거 아니지요. 그런데 저는 이런 아이디어 떠오르지 않아서 꽤 고민했었네요. 
스프링 프레임워크를 사용한다면, Consumer를 DisposableBean를 구현하게 하고
public void destroy() {
    queue.add(new PoisonPill());
}
시스템이 종료할 때, 종료 신호를 받아서 처리하면 될 것 같습니다.

댓글 없음:

댓글 쓰기