簡體   English   中英

如何在 Java 中正確停止線程?

[英]How to properly stop the Thread in Java?

我需要一個解決方案來正確停止 Java 中的線程。

我有實現 Runnable 接口的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.");
        }
    }
}

但是當我關閉 tomcat 時,我的 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。 所以問題是:

如何停止線程而不拋出任何異常?

PS我不想使用.stop(); 方法,因為它已被棄用。

使用Thread.interrupt()是一種完全可以接受的方式。 事實上,它可能比上面建議的標志更可取。 原因是如果您處於可中斷的阻塞調用中(例如Thread.sleep或使用 java.nio Channel 操作),您實際上可以立即擺脫這些調用。

如果使用標志,則必須等待阻塞操作完成,然后才能檢查標志。 在某些情況下,您無論如何都必須這樣做,例如使用不可中斷的標准InputStream / OutputStream

在這種情況下,當一個線程被中斷時,它不會中斷 IO,但是,您可以在代碼中輕松地定期執行此操作(並且您應該在可以安全停止和清理的戰略點執行此操作)

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

就像我說的, Thread.interrupt()的主要優點是您可以立即中斷可中斷的調用,這是標志方法無法做到的。

IndexProcessor類中,您需要一種設置標志的方法,該標志通知線程需要終止,類似於您剛剛在類范圍內使用的變量run

當您希望停止線程時,您可以設置此標志並在線程上調用join()並等待它完成。

通過使用 volatile 變量或使用與用作標志的變量同步的 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.");
        }
    }
}

簡單回答:您可以通過以下兩種常用方式之一在內部停止線程:

  • run 方法命中一個返回子例程。
  • Run 方法完成,並隱式返回。

您還可以從外部停止線程:

  • 調用system.exit (這會殺死您的整個進程)
  • 調用線程對象的interrupt()方法*
  • 查看線程是否有一個聽起來像它可以工作的已實現方法(如kill()stop()

*:期望這是應該停止線程。 但是,發生這種情況時線程實際執行的操作完全取決於開發人員在創建線程實現時所寫的內容。

您在 run 方法實現中看到的一個常見模式是while(boolean){} ,其中布爾值通常是名為isRunning東西,它是其線程類的成員變量,它是可變的,並且通常可由其他線程通過 setter 方法訪問排序,例如kill() { isRunnable=false; } 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();

如果我們使用JDK 1.0,我們可以調用Thread的棄用方法stop()來終止它。 使用stop()是非常危險的,因為它會殺死你的線程,即使它處於重要的事情中。 沒有辦法保護自己,所以如果你發現使用stop()的代碼,你應該皺眉。

我們如何干凈地關閉一個線程?

在Java中,啟動線程很容易,但關閉它們需要大量的關注和努力。

以下是它在Java中的設計方式。 在每個java線程中都有一個名為Interrupt status flag的標志,我們可以從外部設置,即父線程或主線程。 並且線程可能偶爾檢查它並停止執行。 自願.. !! 方法如下:

Thread loop = new Thread(new Runnable() {
@Override
public void run() {
  while (true) {
    if (Thread.interrupted()) {
      break;
    }
    // Continue to do nothing
  }
}}); loop.start(); loop.interrupt();

source: 如何在java中停止線程 多線程

對於同步線程,我更喜歡使用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;
        }

    }
}

當你想完成另一個線程的執行時,在CountDownLatch上執行 countDown 並將線程join主線程:

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();
 }

我沒有讓中斷在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;
 }

有時我會在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();}
        }

    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM