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


276

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();더 이상 사용되지 않기 때문에 방법 을 사용하고 싶지 않습니다.


1
스레드를 절반 만 종료하면 항상 예외가 발생합니다. 정상적인 동작이라면을 잡아서 무시할 수 있습니다 InterruptedException. 이것이 내가 생각하는 것이지만 표준 방법이 어떻습니까?
nhahtdh

나는 스레드를 자주 사용하지 않았기 때문에 스레드가 매우 새롭기 때문에 예외를 무시하는 것이 정상적인 동작인지 알 수 없습니다. 내가 묻는 이유입니다.
Paulius Matulionis 2014 년

대부분의 경우 예외를 무시하고 메소드 처리를 종료하는 것은 정상적인 동작입니다. 이것이 왜 플래그 기반 접근법보다 나은지 아래 답변을 참조하십시오.
Matt

1
B. Goetz의 깔끔한 설명 InterruptedExceptionibm.com/developerworks/library/j-jtp05236 에서 찾을 수 있습니다 .
Daniel

InterruptedException은 문제가 아니며 게시 된 코드의 유일한 문제는 오류로 기록해서는 안된다는 것입니다. 관심이있는 경우 디버그를 제외하고는 모두 기록해야 할 강력한 이유가 없습니다. . 선택한 응답은 유휴 및 대기와 같은 짧은 통화를 끊을 수 없기 때문에 유감입니다.
Nathan Hughes

답변:


173

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.");
        }
    }
}

3
나는 당신이 당신이 그것을 편집하기 직전에 당신의 대답에 예제를 주었던 것과 똑같이했습니다. 좋은 답변입니다! 감사합니다, 이제 모든 :) 완벽하게 작동
Paulius Matulionis

1
스레드 로직이 복잡하고 다른 클래스의 많은 메소드를 호출하면 어떻게됩니까? 어디서나 부울 플래그를 확인할 수 없습니다. 그러면 어떻게해야합니까?
Soteric

Runnable에 대한 신호가 스레드를 종료시키는 방식으로 빌드되도록 코드 디자인을 변경해야합니다. 대부분의 사용은 run 메소드에서이 루프를 가지므로 일반적으로 문제가 없습니다.
DrYap

3
join () 문에서 InterruptedException이 발생하면 어떻게됩니까?
benzaita '11

14
나쁜 충고를 퍼트려 공감했습니다. 수동 롤 방식은 응용 프로그램이 수면이 끝날 때까지 기다려야한다는 것을 의미하며 중단이 발생하면 수면이 짧아집니다. Thread # interrupt를 사용하도록 이것을 쉽게 수정할 수 있습니다.
Nathan Hughes

298

사용하는 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()은 인터럽트 가능한 호출을 즉시 중단 할 수 있다는 것입니다. 플래그 접근으로는 할 수 없습니다.


32
+1 -Ad. Hoc 플래그를 사용하여 동일한 것을 구현하는 경우 Thread.interupt ()가 확실히 바람직합니다.
Stephen C

2
또한 이것이 완벽하고 효율적인 방법이라고 생각합니다. +1
RoboAlex

4
코드에는 작은 오타가 있습니다. Thread.currentThread ()에는 괄호가 없습니다.
Vlad V

1
실제로 스레드와 접촉하는 다른 누군가가 다른 곳에서 스레드를 인터럽트하여 중지하고 디버깅하기가 매우 어려울 수 있으므로 플래그를 사용하는 것은 바람직하지 않습니다. 항상 플래그도 사용하십시오.
JohnyTex

이 특정한 경우에는 호출 interrupt()이 정상일 수 있지만 다른 많은 경우에는 그렇지 않습니다 (예 : 자원을 닫아야하는 경우). 누군가가 루프의 내부 작업을 변경 interrupt()하면 부울 방식으로 변경 해야합니다. 나는 처음부터 안전한 길을 가고 깃발을 사용합니다.
m0skit0

25

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

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

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

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

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

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


3
"이 서브 루틴은 스레드가 종료하기 전에 보유하고있는 모든 자원을 해제 할 수 있기 때문에 좋습니다." 이해가 안 돼요 "공식"중단 상태를 사용하여 스레드의 보류 된 자원을 완벽하게 정리할 수 있습니다. Thread.currentThread (). isInterrupted () 또는 Thread.interrupted () (필요에 맞는 것)를 사용하여 확인하거나 InterruptedException을 포착하고 정리하십시오. 문제는 어디에 있습니까?
프란츠 D.

나는 실행이 돌아올 때 멈추는 것을 이해하지 못했기 때문에 플래그 방법이 왜 작동하는지 이해할 수 없었습니다! 이것은 매우 간단했습니다. 친애하는 선생님, 이것을 지적 해 주셔서 감사합니다.
thahgr

9

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) {
        ...

2
그것은 아니다 Thread- 그것은 구현 Runnable- 당신이 호출 할 수 없습니다 Thread당신이로 선언하지 않는 한 메소드를 Thread당신이 전화를 할 수없는 경우stopExecuting()
돈 치들

7

일반적으로 스레드는 중단되면 종료됩니다. 그렇다면 기본 부울을 사용하지 않는 이유는 무엇입니까? 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 ()을 사용하지 않고;


5

스레드 동기화의 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.");
        } 
    }
}

3

일부 보충 정보. 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();
 }

3
InterruptedException을 무시하지 마십시오. 그것은 다른 코드가 스레드를 종료하도록 명시 적으로 요청하고 있음을 의미합니다. 해당 요청을 무시하는 스레드는 불량 스레드입니다. InterruptedException을 처리하는 올바른 방법은 루프를 종료하는 것입니다.
VGR

2

Android에서 인터럽트가 작동하지 않았 으므로이 방법을 사용하여 완벽하게 작동합니다.

boolean shouldCheckUpdates = true;

private void startupCheckForUpdatesEveryFewSeconds() {
    threadCheckChat = new Thread(new CheckUpdates());
    threadCheckChat.start();
}

private class CheckUpdates implements Runnable{
    public void run() {
        while (shouldCheckUpdates){
            System.out.println("Do your thing here");
        }
    }
}

 public void stop(){
        shouldCheckUpdates = false;
 }

shouldCheckUpdates가 아니기 때문에 실패 할 가능성이 volatile있습니다. docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3을 참조하십시오 .
VGR

0

언젠가 onDestroy () / contextDestroyed ()에서 1000 번 시도 할 것입니다

      @Override
    protected void onDestroy() {
        boolean retry = true;
        int counter = 0;
        while(retry && counter<1000)
        {
            counter++;
            try{thread.setRunnung(false);
                thread.join();
                retry = false;
                thread = null; //garbage can coll
            }catch(InterruptedException e){e.printStackTrace();}
        }

    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.