一、概述
在上一篇文章中,笔者分享并用“学生上课回答问题”的情景解释了Java线程的几个常用方法以及它们的作用。那么,很自然地,我们就会想到线程启动并执行之后,什么时候才能停止或者什么时候让它停止呢?这就是笔者想在这篇文章中和大家分享的主题。
二、stop()方法
Java多线程中有一个已经封装好的方法—–stop()方法。这个方法是在早期版本的Java中就封装好的,但是随着Java的逐步普及,人们发现这种方法存在在相当的缺点。其中最突出的就是,一旦使用stop()方法,那么线程将戛然而止,我们甚至不知道线程在停止之前完成了哪些工作。Sun(即现在的Oracle公司)都不建议使用这种方法来停止线程。
我们仍然用上一篇文章中“学生上课回答问题”这个情景来做演示。将Classroom.java中的部分代码改成如下图所示:
如上图示,我们将原来通过设置StudentRunnable类(定义学生线程的类)中keepRunning属性来停止线程的方式更改为直接使用stop()方法。可以明显的看到,在Eclipse中stop()方法上划有一条横线,这就表明这个方法是deprecated即不建议使用的。运行修改后的代码,得到如下结果:
和修改前相比,我们可以发现在红色箭头标注的地方缺少了“学生A/B回答问题结束”这样的提示语,这样就好比电影中间被突然剪掉一段,这种现象就可以称之为“戛然而止”。
根据上面的分析,我们现在考虑某一个线程正在操作数据库的事务,如果用stop()方法来停止它,就会出现“戛然而止”的情况。但是很多时候,在操作数据库事务达到我们的目的之后可能还要对数据库进行一些清理或者维护(代码位置/作用类似于上述输出“学生A/B回答问题结束”的代码一样)操作,这样简单粗暴地使用stop()方法就不能达到维护的目的。长此以往,可能对于数据库数据造成灾难性的后果。
另外,stop()方法不安全,它会解除由线程获取的所有锁定,当对象处于一种不连贯状态,其他线程就能检查和修改他们,结果很难查出真正的问题所在。
三、interrupt()方法
还有一种说法,即通过Thread中的interrupt()方法来停止线程。我们查阅Java的官方API文档得到如下结果:
根据上图中的描述,interrupt()方法是用于中断线程,并且我们看到调用interrupt()方法后线程将会被设置interrupt status。但是,当线程因为如wait()等方法而阻塞的时候,其interrupt status将被清除。(中断和阻塞是操作系统中的概念)。
下面用一个实际的例子来说明interrupt()方法用于停止线程是不合适的。
Interrupt.java:
package com.bebdong.concurrent;
public class Interrupt extends Thread
{
public static void main(String[] args)
{
Interrupt thread=new Interrupt();
System.out.println("开始线程……");
thread.start();
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("线程中断……");
thread.interrupt();
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("程序结束!");
}
public void run()
{
while(true)
{
System.out.println("线程运行……");
long time=System.currentTimeMillis();
while(System.currentTimeMillis()-time<1000) //空循环,相当于sleep(1000)
{}
}
}
}
运行上述程序可以得到如下结果:
如①位置标记,此时线程已经调用interrupt()方法而中断,但是当主线程(程序)结束之后(②位置标记)该线程并没有停止。
四、suspend()方法
这个方法容易发生死锁,调用此方法时,目标线程会停下来,但仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被挂起的线程恢复运行。对任何线程来说,如果他们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。
五、正确停止线程的方法
那么,什么方法才是停止线程的正确方法呢?现在使用最多的就是通过设置标志(旗标)的方法,正如我们一开始设置的keepRunning属性一样,它就是一个线程是否停止的旗标。
我们再用interrupt()测试的代码稍作修改如下:
PS:(有兴趣的读者可以思考为什么利用空循环来近似模拟sleep(1000)效果,而不直接使用sleep()方法呢?答案就在文章中哦!)
此时,再运行可以得到如下结果:
可以发现线程正常停止了。实质上这样的修改也是通过设置标志的方法,只不过这里的标志是线程是否中断的状态而已。
(未完待续……)