一、错误的方法——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执行结束!



从运行结果可以看到,线程就被正确停止了。