Java에서 스레드를 올바르게 중지하는 방법은 무엇입니까?

Java에서 스레드를 올바르게 중지하는 솔루션이 필요합니다.

나는이 IndexProcessor실행 가능한 인터페이스를 구현하는 클래스를 :

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    @Override
    public void run() {
        boolean run = true;
        while (run) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                run = false;
            }
        }

    }
}

그리고 ServletContextListener스레드를 시작하고 중지하는 클래스가 있습니다.

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        thread = new Thread(new IndexProcessor());
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            thread.interrupt();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

그러나 바람둥이를 종료하면 IndexProcessor 클래스에서 예외가 발생합니다.

2012-06-09 17:04:50,671 [Thread-3] ERROR  IndexProcessor Exception
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at lt.ccl.searchengine.processor.IndexProcessor.run(IndexProcessor.java:22)
    at java.lang.Thread.run(Unknown Source)

JDK 1.6을 사용하고 있습니다. 따라서 질문은 다음과 같습니다.

스레드를 중지하고 예외를 발생시키지 않는 방법은 무엇입니까?

추신 : 나는 .stop();더 이상 사용되지 않기 때문에 방법 을 사용하고 싶지 않습니다.



답변

IndexProcessor클래스 에서는 run클래스 범위에서 방금 사용한 변수와 유사하게 스레드에 종료해야 함을 알리는 플래그를 설정하는 방법이 필요합니다 .

스레드를 중지하려면이 플래그를 설정 join()하고 스레드를 호출 하고 완료 될 때까지 기다리십시오.

휘발성 변수를 사용하거나 플래그로 사용되는 변수와 동기화되는 getter 및 setter 메소드를 사용하여 플래그가 스레드로부터 안전해야합니다.

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                running = false;
            }
        }

    }
}

그런 다음 SearchEngineContextListener:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        runnable = new IndexProcessor();
        thread = new Thread(runnable);
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            runnable.terminate();
            thread.join();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}


답변

사용하는 Thread.interrupt()것이 완벽하게 허용되는 방법입니다. 사실, 위에서 제안한 것처럼 플래그를 선호 할 것입니다. 그 이유는 Thread.sleepjava.nio Channel 작업을 사용하거나 사용하는 것과 같이 인터럽트 가능한 차단 호출을 수행하는 경우 실제로 즉시 차단할 수 있기 때문입니다.

플래그를 사용하는 경우 차단 작업이 완료 될 때까지 기다려야합니다. 그러면 플래그를 확인할 수 있습니다. 어떤 경우 에는 중단 할 수없는 표준 InputStream/ OutputStream을 사용하는 것과 같이 어쨌든이 작업을 수행해야합니다 .

이 경우 스레드가 중단되면 IO가 중단되지 않지만 코드에서 쉽게 일상적으로 수행 할 수 있습니다 (안전하게 중지하고 정리 할 수있는 전략적 지점 에서이 작업을 수행해야 함)

if (Thread.currentThread().isInterrupted()) {
  // cleanup and stop execution
  // for example a break in a loop
}

내가 말했듯이, 주요 장점 Thread.interrupt()은 인터럽트 가능한 호출을 즉시 중단 할 수 있다는 것입니다. 플래그 접근으로는 할 수 없습니다.


답변

간단한 대답 : 두 가지 일반적인 방법 중 하나로 스레드를 내부적으로 중지 할 수 있습니다.

  • run 메소드는 리턴 서브 루틴에 도달합니다.
  • Run 메소드가 완료되고 내재적으로 리턴됩니다.

스레드를 외부 적으로 중지 할 수도 있습니다.

  • 전화 system.exit(전체 프로세스를 종료)
  • 스레드 객체의 interrupt()메소드 호출 *
  • 스레드가 같은 소리 일 것이라고 구현 된 방법을 (같은이 있는지 kill()또는 stop())

* : 스레드를 중지해야합니다. 그러나 스레드가 실제로 발생할 때 스레드가 실제로하는 것은 개발자가 스레드 구현을 작성할 때 작성한 내용에 달려 있습니다.

run 메소드 구현에서 볼 수있는 일반적인 패턴은이며 while(boolean){}, 부울은 일반적으로 이름이 지정된 것으로 isRunning, 스레드 클래스의 멤버 변수이고, 휘발성이며, 일반적으로 setter 메소드 (예 :)를 통해 다른 스레드가 액세스 할 수 있습니다 kill() { isRunnable=false; }. 이 서브 루틴은 스레드가 종료하기 전에 보유한 모든 자원을 해제 할 수있게 해주므로 좋습니다.


답변

run()루프 에서 플래그를 검사하여 스레드를 항상 종료해야 합니다 (있는 경우).

스레드는 다음과 같아야합니다.

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean execute;

    @Override
    public void run() {
        this.execute = true;
        while (this.execute) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                this.execute = false;
            }
        }
    }

    public void stopExecuting() {
        this.execute = false;
    }
}

그런 다음을 호출하여 스레드를 종료 할 수 있습니다 thread.stopExecuting(). 이렇게하면 실이 깨끗하게 종료되지만 최대 15 초가 걸립니다 (수면으로 인해). 정말 긴급한 경우 thread.interrupt ()를 호출 할 수 있지만 선호하는 방법은 항상 플래그를 확인해야합니다.

15 초 동안 기다리지 않으려면 다음과 같이 절전 모드를 분할하면됩니다.

        ...
        try {
            LOGGER.debug("Sleeping...");
            for (int i = 0; (i < 150) && this.execute; i++) {
                Thread.sleep((long) 100);
            }

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...


답변

일반적으로 스레드는 중단되면 종료됩니다. 그렇다면 기본 부울을 사용하지 않는 이유는 무엇입니까? isInterrupted ()를 시도하십시오 :

Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

ref- 스레드를 어떻게 죽일 수 있습니까? stop ()을 사용하지 않고;


답변

스레드 동기화의 CountDownLatch경우 프로세스가 완료 될 때까지 스레드가 대기하는 데 도움이되는 사용 을 선호합니다 . 이 경우 작업자 클래스는 CountDownLatch주어진 개수 의 인스턴스로 설정됩니다 . 호출에 await현재 카운트가 도달의 호출에 의한 제로까지있어서 차단 countDown도달 법 초과 세트. 이 방법을 사용하면 지정된 대기 시간이 경과 할 때까지 기다리지 않고도 스레드를 즉시 중단 할 수 있습니다.

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    private final CountDownLatch countdownlatch;
    public IndexProcessor(CountDownLatch countdownlatch) {
        this.countdownlatch = countdownlatch;
    }


    public void run() {
        try {
            while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) {
                LOGGER.debug("Processing...");
            }
        } catch (InterruptedException e) {
            LOGGER.error("Exception", e);
            run = false;
        }

    }
}

다른 스레드의 실행을 마치려면 CountDownLatchjoin에서 스레드를 메인 스레드로 countDown을 실행 하십시오.

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;
    private CountDownLatch countdownLatch = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        countdownLatch = new CountDownLatch(1);
        Thread thread = new Thread(new IndexProcessor(countdownLatch));
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (countdownLatch != null)
        {
            countdownLatch.countDown();
        }
        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
            }
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}


답변

일부 보충 정보. Java 문서에서는 플래그와 인터럽트가 모두 제안됩니다.

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

private volatile Thread blinker;

public void stop() {
    blinker = null;
}

public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

오랜 시간 동안 대기하는 스레드 (예 : 입력)의 경우 Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }