티스토리 뷰

발생일: 2009.04.16

문제:
어플리케이션의 뒷단에서 작동하는 Daemon Thread를 생성하려고 한다.
어플리케이션이 로드되면 스레드가 일정 시간동안 작동하며,
스레드는 중복되어 작동되지 않도록 한다.

해결책:
1.
데몬 스레드의 경우, 유저 스레드(일반 스레드)와 달리 JVM의 뒷단에서 실행되기 때문에
JVM이 종료되면 함께 종료된다. (데몬 스레드와 유저 스레드의 차이)

일단 데몬 스레드는 아래와 같이 Thead의 setDaemon(true) 메서드를 통해 설정하면 된다.

Thread t = new Thread(this);
t.setDaemon(true); // 스레드를 데몬으로 설정


2.
어플리케이션의 백그라운드 태스크로 추가하기 위해서는,
일단 데몬 스레드가 포함된 클래스를 컨텍스트의 리스너로 등록해줘야 한다.

ServletContextListener 를 구현하고 해당 클래스를 web.xml 에 리스너로 추가하면 되겠다.

contextInitialized 메서드에서 스레드를 생성하여 실행시키고,
contextDestroyed 메서드에서는 해당 스레드를 null 로 설정해주는 게 좋겠다.


3.
최종적으로 아래처럼 샘플을 완성했다.

public class SampleDaemon implements ServletContextListener, Runnable { // (1)
    /** 작업을 수행할 thread */
    private Thread thread;
   
    /** context */
    private ServletContext sc; // (2)
   
    /** 작업을 수행한다 */
    public void startDaemon() {
        if (thread == null) { // (3)
            thread = new Thread(this, "Daemon thread for background task");
            thread.setDaemon(true);
        }
       
        if (!thread.isAlive()) { // (4)
            thread.start();
        }
    }

    /** 스레드가 실제로 작업하는 부분 */
    public void run() {
        Thread currentThread = Thread.currentThread(); // (5)
       
        while (currentThread == thread) { // (5)
            try {
                Thread.sleep(1000 * 60); // (6)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 실제 작업이 여기에 들어간다.
if (currentThread == thread) { // (8)
              doSomething();
            }
        }
    }

    /** 컨텍스트 초기화 시 데몬 스레드를 작동한다 */
    public void contextInitialized(ServletContextEvent event) {
        sc = event.getServletContext(); // (2)
        startDaemon();
    }
 
    /** 컨텍스트 종료 시 thread를 종료시킨다 */
    public void contextDestroyed(ServletContextEvent event) {
        thread = null; // (7)
    }
}


(1) ServletContextListener 와 Runnable 을 구현해야 한다.
(2) 스레드가 수행할 작업에 서블릿컨텍스트가 필요하다면 멤버로 넣고 컨텍스트 초기화 시 설정해준다.
(3) thread가 null 일 경우에만 새 스레드를 생성하고(싱글턴) , 데몬 스레드로 설정한다.
(4) thread가 작동 중이 아니라면 시작해준다.
(5) Thread로부터 현재 스레드를 가져와서 멤버에 등록된 thread가 현재 작동 중인 thread일 때에만 반복한다.
     이는 while(true) ~ thread.stop 등의 메모리 누수에 따른 대체 방법이다.
     (Thread.stop,Thread.suspend, Thread.resume, 및 Runtime.runFinalizersOnExit 가 추천 되지 않는 이유에 대한
     기사를 참고하자)
(6) 1분 단위로 작업을 수행한다.
     해당 thread를 struts에서 테스트했는데 어플리케이션이 모두 로드되기 전에 작업이 수행되어,
     정상적으로 작업이 되지 않는 경우가 발생했다.
     하여 위와 같이 먼저 sleep 한 후에, 작업을 수행하도록 했다.
(7) 컨텍스트 종료 시 thread 를 null 로 설정하여 thread의 작업을 종료시켜준다.
(8) 동적 컴파일 시 NoClassDefFoundError가 발생하여, 수행 전 재확인하는 로직 추가
     (Daemon Thread에서 NoClassDefFoundError 참고)


web.xml 에는 아래처럼 리스너를 추가해준다.
<listener>
    <listener-class>sample.SampleDaemon</listener-class>
</listener>


Apache Commons 에 Daemon 라이브러리가 있다.
    실제 적용하고자 하는 것과는 다르지만 소스를 열어 참고하면 좋을 듯 하다.

반응형
댓글
공지사항