我需要一个解决方案来正确地停止Java中的线程。

我有IndexProcessorclass,它实现了可运行接口:

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。所以问题是:

我如何停止线程而不抛出任何异常?

附注:我不想使用.stop();方法,因为它已弃用。


当前回答

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

其他回答

对于同步线程,我更喜欢使用CountDownLatch,它可以帮助线程等待进程执行完成。在本例中,使用具有给定计数的CountDownLatch实例来设置工作者类。由于调用了countDown方法或达到了超时设置,await方法的调用将阻塞到当前计数为零。这种方法允许立即中断线程,而不需要等待指定的等待时间过去:

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,并将该线程加入主线程:

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

在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()循环中的标志(如果有的话)来结束线程。

你的线程应该是这样的:

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. stopexecution()结束线程。这样线程就干净地结束了,但这需要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) {
        ...

一些补充信息。 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();
 }

Brian Goetz在他的书中建议使用Thread.currentThread(). isinterrupted()标志和interrupt()方法来取消。

阻塞库方法(如sleep()和wait())尝试检测线程何时被中断并提前返回。它们通过清除中断状态并抛出InterruptedException来响应中断,这表明阻塞操作由于中断而提前完成。

JVM不保证阻塞方法检测中断的速度有多快,但在实践中,检测中断的速度相当快。

class PrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;

    PrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted()) {
                queue.put(p = p.nextProbablePrime()); // blocking operation
            }
        } catch (InterruptedException consumed) {
            // allow thread to exit
        }
        // any code here will still be executed
    }

    public void cancel() {
        interrupt();
    }
}

如果您将任何代码放在catch块之后,当我们吞咽InterruptedException以优雅地退出run()时,它仍然会被执行。

简单介绍一下interrupt()的工作原理。

如果在非阻塞的线程上调用interrupt,则interrupt()不会在run()内部引起InterruptedException,而只是将isInterrupted标志更改为true,线程将继续工作,直到到达thread . currentthread ().isInterrupted()检查并退出run()。

如果在阻塞的线程上调用interrupt (sleep()或wait()被调用,在我们的例子中,它是put()可能阻塞线程),那么isInterrupted将被设置为false,并且在put()中抛出InterruptedException。