一、错误的方法——stop()方法
在1.0版本的jdk中,提供了一个stop方法来停止线程,但是这个方法现在已经被废弃了,因为使用这个方法停止线程,将会使线程戛然而止,我们甚至不知道程序执行到了哪里,资源是否已经释放,会出现一些不可预料的结果。
使用stop()方法停止线程实例:
定义一个线程类YieldRunnable.java
public class YieldRunnable implements Runnable{
public volatile boolean isRunning = true;
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "开始执行!");
while(isRunning) {
for(int i = 1; i < 6; i++) {
System.out.println(name + "执行了[" + i + "]次");
//注意,yield是静态方法
Thread.yield();
}
}
System.out.println(name + "执行结束!");
}
}
接下来是main方法
public static void main(String[] args) {
System.out.println("主线程开始执行!");
YieldRunnable runnable = new YieldRunnable();
Thread thread = new Thread(runnable, "线程1");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
System.out.println("主线程执行结束!");
}
截取部分执行结果片段:
……
线程1执行了[1]次
线程1执行了[2]次
线程1执行了[3]次
线程1执行了[4]次
线程1执行了[5]次
线程1执行了[1]次线程1执行了[1]次主线程执行结束!
从执行结果可以看出,在调用了stop方法之后,线程中的循环语句还没有执行结束线程就戛然而止了,甚至连最后执行结束的输出都没有打印出来,我们甚至不知道程序执行到哪里了,所以stop()方法是不推荐使用的。
二、正确的方法——使用中断信号
所谓使用中断信号,就是定义一个布尔型的标志位,用于标识线程是否继续执行,线程不定期检查该标志位,当需要将线程停止时,将标志位修改为中断,那么线程就可以被正确停止了。
使用中断信号停止线程实例:
YieldRunnable.java与上例相同,不做修改,写一个main方法
public static void main(String[] args) {
System.out.println("主线程开始执行!");
YieldRunnable runnable = new YieldRunnable();
Thread thread = new Thread(runnable, "线程1");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runnable.isRunning = false;
System.out.println("主线程执行结束!");
}
上面的程序将YieldRunnable类中的isRunning设为中断信号,刚开始主线程运行,启动了子线程,之后主线程将子线程的中断信号设为false,子线程读取到中断信号之后就结束线程。
运行结果:
……
线程1执行了[4]次
线程1执行了[5]次
线程1执行了[1]次
线程1执行了[2]次
线程1执行了[3]次
线程1执行了[4]次
线程1执行了[5]次
主线程执行结束!
线程1执行结束!
由运行结果可以发现,子线程正确执行了所有的循环,并打印退出信息正确退出了。
三、另一种值得推敲的方法,interrupt()方法
在Java中还提供了一个方法,interrupt()方法,在jdk文档中这样描述:中断线程,这个方法真的能将线程停止吗?现在用一个实例尝试一下。
先写一个线程YieldRunnable.java
public class YieldRunnable implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "开始执行!");
while(true) {
for(int i = 1; i < 6; i++) {
System.out.println(name + "执行了[" + i + "]次");
//注意,yield是静态方法
Thread.yield();
}
long time = System.currentTimeMillis();
while(System.currentTimeMillis() - time < 1000) {
/*
* 使用while循环模拟 sleep 方法,这里不要使用sleep,具体原因下面再说
*/
}
}
}
}
再写一个main方法
public static void main(String[] args) {
System.out.println("主线程开始执行!");
YieldRunnable runnable = new YieldRunnable();
Thread thread = new Thread(runnable, "线程1");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("主线程执行结束!");
}
运行之后会发现,虽然执行了interrupt()方法,但是子线程并没有停下来,反而是一直执行下去,由此可以得知,interrupt()方法单独使用不能停止线程。那该如何使用interrupt()方法让它能够来停止线程呢?查阅了jdk之后,我们发现线程保有一个状态叫做中断状态,在调用interrupt()之后,线程的中断状态为true,Java提供两种方法获取线程的中断状态:
public static boolean interrupted();
public boolean isInterrupted()
其中,interrupted()方法是静态的,同时调用了interrupted()方法之后线程的中断状态由该方法清除,换句话说,如果连续两次调用该方法,则第二次调用将返回 false。isInterrupted()方法则不会清除线程的中断状态。
现在修改上面的示例代码:
YieldRunnable.java
public class YieldRunnable implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "开始执行!");
while(!Thread.currentThread().isInterrupted()) {
for(int i = 1; i < 6; i++) {
System.out.println(name + "执行了[" + i + "]次");
//注意,yield是静态方法
Thread.yield();
}
long time = System.currentTimeMillis();
while(System.currentTimeMillis() - time < 1000) {
/*
* 使用while循环模拟 sleep 方法,这里不要使用sleep,具体原因下面再说
*/
}
}
System.out.println(name + "执行结束!");
}
}
执行结果:
主线程开始执行!
线程1开始执行!
线程1执行了[1]次
线程1执行了[2]次
线程1执行了[3]次
线程1执行了[4]次
线程1执行了[5]次
主线程执行结束!
线程1执行结束!
从执行结果可以发现线程正确停止了,其实从代码中可以发现,要想使线程停止,其实还是判断的线程的中断状态,检查的是线程isInterrupted()方法的值,这其实也是一种中断信号。
在上面的线程代码中有一段循环,while(System.currentTimeMillis() - time < 1000),这个方法是模拟Thread的sleep(long l)方法的,那为什么这里不使用sleep()方法呢?我们从jdk文档里可以看到:当线程调用了interrupt()方法后,如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。也就是说线程不仅不会被中断,系统还会抛出一个异常。尝试修改一下线程的代码:
public class YieldRunnable implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "开始执行!");
while(!Thread.currentThread().isInterrupted()) {
for(int i = 1; i < 6; i++) {
System.out.println(name + "执行了[" + i + "]次");
//注意,yield是静态方法
Thread.yield();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "执行结束!");
}
}
执行结果:
主线程开始执行!
线程1开始执行!
线程1执行了[1]次
线程1执行了[2]次
线程1执行了[3]次
线程1执行了[4]次
线程1执行了[5]次
线程1执行了[1]次java.lang.InterruptedException: sleep interrupted
主线程执行结束!
线程1执行了[2]次
线程1执行了[3]次
……
不仅线程没有结束,在执行sleep()方法的时候还抛出了一个异常。那么对于这种情形下该如何处理才能使线程正确停止呢?一种可行的方法是在sleep()方法抛出异常时,在处理异常的时候再调用interrupt()方法将线程中止。代码如下:
public class YieldRunnable implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "开始执行!");
while(!Thread.currentThread().isInterrupted()) {
for(int i = 1; i < 6; i++) {
System.out.println(name + "执行了[" + i + "]次");
//注意,yield是静态方法
Thread.yield();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(name + "执行结束!");
}
}
运行结果:
主线程开始执行!
线程1开始执行!
线程1执行了[1]次
线程1执行了[2]次
线程1执行了[3]次
线程1执行了[4]次
线程1执行了[5]次
主线程执行结束!
线程1执行结束!
从运行结果可以看到,线程就被正确停止了。