Java如何正确停止线程(三种场景)

简介

俗话说:上山容易下山难。知道如何启动线程,那么到底如何停止线程呢?本文将讲解Java中三种场景下如何正确的停止线程,分别是普通情况、堵塞状态、循环中堵塞状态,三种情况下如何正确的停止线程。

场景一:普通场景下如何停止线程

如何停止线程:

我们只能调用线程的interrupt()方法通知系统停止线程,并不能强制停止线程。线程能否停止,何时停止,取决于系统。

注意

Java中线程的stop()suspend()resume()三个方法都已经被弃用,所以不再使用stop()方法停止线程。

1.代码演示

代码逻辑描述:

创建一个子线程,子线程汇总循环打印数字。然后我们在其他线程中,调用子线程的interrupt()方法停止线程,观察停止前后的控制台输出情况,理解上述线程停止的含义。

public class StopNormalThread {

	public static void main(String[] args) throws InterruptedException {
		// 最好的停止线程方式:通过interrupt通知线程停止线程;而且只能通知,并不能强制让其停止。
		testInterruptThread();

	}

	/**
	 * 线程只能通知停止,不能强制立刻停止测试。
	 */
	private static void testInterruptThread() throws InterruptedException {
		Thread thread = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i <= 1000000; i++) {
					// 判断如果线程没有被中断,则继续输出
					if (!Thread.currentThread().isInterrupted()) {
						System.out.println("当前输出位置:" + i);
					}
				}
			}
		});
		thread.start();
		System.out.println("子线程已经启动");
		//主线程休眠,让子线程跑一会儿,然后让子线程停止
		Thread.sleep(1000);
		System.out.println("主线程休眠结束,开始停止子线程");
		// 终止后,发现for循环还会继续输出内容,少许时间后才停止。说明我们无法控制线程立刻停止。
		thread.interrupt();
		System.out.println("子线程已被停止");
	}

}
程序输出节选:
子线程已经启动
当前输出位置:0
当前输出位置:1
当前输出位置:2
略……
当前输出位置:430290
当前输出位置:430291
当前输出位置:430292
主线程休眠结束,开始停止子线程
当前输出位置:430293
当前输出位置:430294
当前输出位置:430295
当前输出位置:430296
当前输出位置:430297
当前输出位置:430298
当前输出位置:430299
当前输出位置:430300
当前输出位置:430301
当前输出位置:430302
当前输出位置:430303
子线程已被停止

Process finished with exit code 0

运行结果解释:

我们可以看出,子线程创建并启动后,开始输出数字,当主线程中调用thread.interrupt()方法时,即通知系统要停止子线程的运行了,此时控制台中还是会有数字继续输出,这就表明:我们只能通过thread.interrupt()方法通知系统停止子线程,但子线程可能不会立即停止,可能还会继续运行一段时间才会停止。

场景一:普通场景下通过调用thread.interrupt()方法停止线程

场景二:堵塞状态下如何停止线程

什么是堵塞状态:阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,即还没有真正运行Synchronized修饰的代码时的状态。BLOCKED或WAITING或TIME_WAITING这三种统称为堵塞状态

关于线程状态的内容,如不有不明白的地方,可参考:《Java线程状态完全解析教程》

代码逻辑描述:

在主线程中创建一个子线程并运行,然后休眠两秒后,让子线程停止。

在子线程中,循环打印数字,然后让子线程休眠1秒(此时子线程进入阻塞状态),然后子线程会被停止。

public class StopBlockThread {

	public static void main(String[] args) throws InterruptedException {
		testBlockingInterruptThread();

	}

	/**
	 * 中止堵塞状态的线程示例
	 */
	private static void testBlockingInterruptThread() {
		try {
			Thread thread = new Thread(new Runnable() {
				public void run() {
					for (int i = 0; i <= 1000000; i++) {
						// 判断如果线程没有被中断,则继续输出
						if (!Thread.currentThread().isInterrupted()) {
							System.out.println("当前输出位置:" + i);
						}
					}
					try {
						// 模拟线程堵塞
						System.out.println("--1--模拟线程堵塞中");
						Thread.sleep(1000);
						System.out.println("--2--此行不会被打印,即线程以被停止");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
			thread.start();
			System.out.println("让子线程运行两秒,然后再通知其停止");
			Thread.sleep(2000);
			/*让子线程for循环在没有循环结束时,接收到停止信号,此时子线程停止,并执行sleep(模拟阻塞状态)时,
			会抛出sleep interrupted中断异常,表示堵塞状态也被中断了,即堵塞状态的线程成功被终止了 */
			thread.interrupt();
			System.out.println("通知停止线程");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
程序输出节选:
让子线程运行两秒,然后再通知其停止
当前输出位置:0
当前输出位置:1
当前输出位置:2
略……
当前输出位置:897419
当前输出位置:897420
当前输出位置:897421
通知停止线程
--1--模拟线程堵塞中
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.stop.StopBlockThread$1.run(StopBlockThread.java:31)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0
运行结果解释:

分析下子线程的运行流程,子线程启动后循环打印数字,打印语句有一个判断条件!Thread.currentThread().isInterrupted(),意思是只要不被中断才打印,若中断了,就不再打印,转而往下执行Thread.sleep(1000)语句,进入堵塞状态。

此时主线程调用了thread.interrupt(),系统尝试中断子线程,发现子线程在阻塞状态中,所以会抛出异常sleep interrupted,然后我们发现控制台输出Process finished with exit code 0,表示线程被正常停止了。

场景二:阻塞状态下也可以通过thread.interrupt()停止线程

场景三:循环中堵塞状态下如何停止线程

代码逻辑描述:

主线程中创建一个子线程并运行,两秒钟后,停止子线程。

子线程run()方法中,写一个循环打印数字逻辑,并在每次循环中,都调用一次Thread.sleep(20),目的是让每次循环都进入堵塞状态。此时要想正常停止线程,必须要在循环外部增加try-catch语句,即当阻塞被停止时,会抛出异常,此时即可终止循环,停止线程

public class StopLoopBlockThread {

	public static void main(String[] args) {
		testLoopBlockStopThread();
	}

	/**
	 * 循环中存在堵塞的线程停止示例(关键是将循环放到try-catch内部才生效)
	 */
	private static void testLoopBlockStopThread() {

		Thread thread = new Thread(new Runnable() {
			public void run() {
				try {
					/* 此处不需要判断线程是否已经被中断了,因为如果在循环中的休眠过程中(堵塞时),
					 收到interrupt信号,则会立刻抛出停止休眠异常*/
					for (int i = 0; i < 1000; i++) {
						System.out.println("当前输出位置:" + i);
						// 模拟每次循环都堵塞
						Thread.sleep(20);
					}
				} catch (InterruptedException e) {
					/* try-catch一定放在循环外部,否则线程将不会停止。因为中断异常在循环中被捕获,
					但循环并没有满足循环停止条件,所以知道循环运行结束,才会停止。即时在循环终止条件中,
					添加`!Thread.currentThread().isInterrupted()`判断,循环也不会停止,因为线程的sleep()方法,
					一旦抛出被中断异常后,其isInterrupted标记也会被清除,所以也无法立即停止线程*/
					e.printStackTrace();
				}
			}
		});

		thread.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread.interrupt();
	}

}
程序输出节选:
当前输出位置:0
当前输出位置:1
当前输出位置:2
当前输出位置:3
略...
当前输出位置:95
当前输出位置:96
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.stop.StopLoopBlockThread$1.run(StopLoopBlockThread.java:27)
	at java.lang.Thread.run(Thread.java:748)
	
Process finished with exit code 0
运行结果解释:

本例最关键地方在于如果仅仅把try-catch语句包裹子线程中的在Thread.sleep(20)上,一旦接收到终止信号,程序开始终止sleep()方法的阻塞状态会抛出异常,此时异常将在循环体内被捕获,循环并不能终止,子线程还是会继续运行,一直运行到for循环结束才能正常停止。这样就不符合我们预期,我们想让程序尽快的做出停止操作,如果程序没有终止,则会造成很多不可挽回的结果。

场景三:循环中堵塞状态下正确的停止线程,也可以通过thread.interrupt()停止线程,但需要在子线程的循环外部增加try-catch代码块,捕获到中止堵塞状态异常时,也能停止线程。

总结

本文介绍了线程在三种场景下的停止方式,都是通过interrupt()方法来停止的,但特殊的是停止循环中的阻塞线程时,需要在循环外部增加try-catch代码块,捕获到中止堵塞状态异常时停止线程。希望这篇文章可以让你掌握如何在多线程编程中,正确的停止线程。喜欢本文请收藏、点赞、关注。

参考资料补充:
关于多线程、synchronized关键字wait()notify()方法的系列教程,请参考以下文章:

《Java线程状态完全解析教程》

《Java中synchronized实现类锁的两种方式及原理解析》

《Java中synchronized实现对象锁的两种方式及原理解析》

《Java多线程wait()和notify()系列方法使用教程》

《Java多线程中notifyAll()方法使用教程》

《Java两个线程交替打印奇偶数(两种方法对比)》

《Java中Synchronized的可重入性和不可中断性的分析和代码验证》

《Java多线程访问Synchronized同步方法的八种使用场景》

《Java官方文档创建线程的两种方式及优点和缺点分析》

《Java中线程安全和线程不安全解析和示例》

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值