我需要一个解决方案来正确地停止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();方法,因为它已弃用。
您应该始终通过检查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();
}
使用Thread.interrupt()是一种完全可以接受的方法。事实上,它可能比上面建议的标志更可取。原因是,如果你在一个可中断的阻塞调用(如线程。睡眠或使用java。nio Channel operations),你就可以马上跳出来。
如果你使用一个标志,你必须等待阻塞操作完成,然后你可以检查你的标志。在某些情况下,无论如何你都必须这样做,比如使用标准的不可中断的InputStream/OutputStream。
在这种情况下,当线程中断时,它不会中断IO,但是,您可以在代码中轻松地常规执行此操作(并且应该在可以安全停止和清理的战略点执行此操作)。
if (Thread.currentThread().isInterrupted()) {
// cleanup and stop execution
// for example a break in a loop
}
就像我说的,Thread.interrupt()的主要优点是可以立即中断可中断调用,这是用flag方法做不到的。
对于同步线程,我更喜欢使用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.");
}
}
}
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。