复杂点的Java应用一般都会开多个线程在后台跑程序,有时候因为发布或其他原因需要停止程序,那就涉及到如何停止Java线程的问题.Java 的线程停止还是比较别扭的,如果是从其他语言转学java,很容易掉进坑里面.
如果初学Java,在看到Thread类有个stop方法,那第一反应停止应该用stop吧。在好些的IDE里面会给你提示,此方法已经被标记为:@Deprecated
,为什么那。那是因为stop是直接停止线程,为什么直接停止程序不行那,因程序可能还没有处理完内存中积压的数据,直接强制停止,就可能会造成数据的完整性被破坏,和我们尽量不要用kill -9 pid
来这种暴力停止程序的原因一样。
那就继续找:suspend
和resume
看到这两个方法,将线程暂停的时候,也许会用到此方法。结果这个也是坑,这两个方法同样被标记为:@Deprecated
,为什么这种暂停的方法也不建议用那,这是因为java的线程在调用suspend暂停后,并没有释放所持有的锁,这样如果其他线程刚好必须用到这把锁,迟迟获取不到,就可能进入到死锁状态。
这个是从其他语言转Java的人常用的一种停止线程的方法,这种方法我以前经常用,觉得简单方便,第一次看到用这种方法停止线程是错误的,真令我惊讶。当然不是所有的情况下,都会有问题,在特定的情况下,线程会无法终止。
实例代码如下:生产者代码:
class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 30 == 0) {
storage.put(num);
System.out.println(num + "放入队列中。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
消费者代码:
import java.util.concurrent.BlockingQueue;
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.89) {
return false;
}
return true;
}
}
main函数代码:
import java.util.concurrent.ArrayBlockingQueue;
public class TestStopThread
{
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(800);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者停止。");
// 停止标识设置为true 停止生产者
producer.canceled = true;
System.out.println(producer.canceled);
}
}
代码比较简单,生产者和消费者通过ArrayBlockingQueue
队列相连,生产者生产30倍数的数字,消费者消费,消费者每消费一个数字就会取个随机数看下是否大于0.89,大于则消费者停止消费。主线程设置生产者停止标识,标识生产者可以停止了,停止后,整个程序也应该停止了。运行结果:从结果可以看出,虽然生产者的停止标识为true了,但是整个程序仍然没有停止。这是因为在生产者的循环中,storage.put(num);
队列满了之后,发生了阻塞,生产者的线程就阻塞在这里面,虽然canceled标识已经设置为true了,但是程序还没有到判断循环标志着一步,所以一直是卡着的。
Java线程停止的正确姿势,是采用中断方式,把生产者的核心代码改动如下:
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && ! Thread.currentThread().isInterrupted()) {
if (num % 30 == 0) {
storage.put(num);
System.out.println(num + "放入队列中。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程检测到中断信号");
} finally {
System.out.println("生产者结束运行");
}
}
主函数改成通过中断方式来通知生产者线程停止:
producerThread.interrupt();
运行结果:可以看到中断可以打断线程的阻塞,让线程继续执行下去,而且如果线程处于sleep状态,中断仍然可以打断sleep状态,这样就不用像刚才的线程标识那样,还必须等待线程休眠完毕后才可以响应。中断发生后,我们还可以继续处理剩下的数据,这样就不像stop()
方法那样处理的太生硬,而导致数据不一致问题。
isInterrupted()
会检测中断标识,检测到返回true,并将标志设置成false。所以我们在编写多线程的处理任务代码的时候,不要将中断吞并而不处理,特别有些人喜欢用catch(Execption e)
方式来捕获异常,而不处理。如果框架采用这种中断方式来停止线程,那就无法停止线程。
实话实说,我觉得java这种停线程的方式很容易让人误用,没办法,就这样设计的。
浣溪沙·山色横侵蘸晕霞
[宋代] [苏轼]
山色横侵蘸晕霞,湘川风静吐寒花。远林屋散尚啼鸦。
梦到故园多少路,酒醒南望隔天涯。月明千里照平沙。