当前位置:网站首页>并发编程 — 如何中断/停止一个运行中的线程?

并发编程 — 如何中断/停止一个运行中的线程?

2022-07-05 06:40:00 搬运Gong

如何正确的中断或者停止一个正在运行的线程呢?这是一道非常经典的面试题,里面涵盖了不少的知识点,读完本文,带你彻底掌握正确中断、停止线程的方法。

 什么是中断机制?

首先

一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。

所以,Thread.stop,Thread.suspend,Thread.resume 都已经被废弃了。

其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制--中断,也即中断标识协商机制。

中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断。

此时究竟该做什么需要你自己写代码实现。

每个线程对象中都有一个中断标识位,用于表示线程是否被中断,该标识位为true表示中断,为false表示未中断;

通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

线程(Thread类)中断的 API 方法

既然有线程中断的业务场景,那么Java 的大牛们必然也想到了,并提供了相应的 API 方法来完成,下面一起来看一下都有哪些,它们又是如何实现的。

void interrupt()

实例方法,仅仅是设置线程的中断状态为 true,发起一个协商而不会立刻停止线程。

static boolean interrupted()

静态方法,Thread.interrupted();

判断线程是否被中断并清除当前中断状态。

这个方法做了两件事:

1. 返回当前线程的中断状态,测试当前线程是否已被中断

2.将当前线程的中断状态清零并重新设置为 false,清除线程的中断状态

此方法有点不好理解,如果连续两次调用此方法,则第二次调用将返回 false,因为连续调用两次的结果可能不一样

boolean isInterrupted()

实例方法

判断当前线程是否被中断(通过检查中断标志位)

 如何停止中断运行中线程?

1. 通过 volatile 设置共享变量

public class InterruptedDemo {

	/**
	* 线程中断标志位
	*/
	static volatile boolean stopFlag = false;

	public static void main(String[] args) {
		new Thread(() -> {
			while (true) {
				if (stopFlag) {
					System.out.println(Thread.currentThread().getName() + " 线程中断,停止运行!");
					break;
				}
				System.out.println(Thread.currentThread().getName() + " 线程启动,开始搬砖...");
			}

		},"t1").start();

		try { TimeUnit.MILLISECONDS.sleep(1L); } catch (InterruptedException e) { e.printStackTrace(); }
		// 主线程告知t1 线程,需要中断了
		stopFlag = true;
	}
}

2. AtomicBoolean 原子类

static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
	new Thread(() -> {
		while (true) {
			if (atomicBoolean.get()) {
				System.out.println(Thread.currentThread().getName() + " 线程中断,停止运行!");
				break;
			}
			System.out.println(Thread.currentThread().getName() + " 线程启动,开始搬砖...");
		}
	},"t1").start();
	try { TimeUnit.MILLISECONDS.sleep(1L); } catch (InterruptedException e) { e.printStackTrace(); }
	// 主线程告知t1 线程,需要中断了
	atomicBoolean.set(true);

这个和上面的 volatile 一样,同样可以实现。

3. Thread API

public static void main(String[] args) {
	Thread t1 = new Thread(() -> {
		while (true) {
			if (Thread.currentThread().isInterrupted()) {
				System.out.println(Thread.currentThread().getName() + " 线程中断,停止运行!");
				break;
			}
			System.out.println(Thread.currentThread().getName() + " 线程启动,开始搬砖...");
		}
	},"t1");
	t1.start();
	try { TimeUnit.MILLISECONDS.sleep(1L); } catch (InterruptedException e) { e.printStackTrace(); }
	// t2线程通知 t1 线程中断运行
	new Thread(() -> t1.interrupt(),"t2").start();
}

4. interrupt()、isInterrupted()源码分析

 调用了底层的 native 方法 interrupt0(),仅仅是设置了一个标志而已。

重置标志位为 false。

通过查看源码,得出总结如下:

当对一个线程,调用interrupt()时:
① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已

被设置中断标志的线程将继续正常运行,不受影响。
所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。

②如果线程处于被阻塞状态(例如处于sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

下面通过一个简单 case 来演示一下得出的总结:

  • 验证对线程发出中断命令interrupt后,不会立刻执行中断
public static void main(String[] args) {
	Thread t1 = new Thread(() -> {
		for (int i = 0; i < 200; i++) {
			System.out.println("----> " + i);
		}
		// 此时,验证 t1 线程是否收到了中断请求后,立刻终止执行了此处的输出?
		System.out.println("t1线程的中断标志位02 --> " + Thread.currentThread().isInterrupted()); // true
	},"t1");
	t1.start();
	System.out.println("t1线程的默认中断标志位--> " + t1.isInterrupted()); // false
	try { TimeUnit.MILLISECONDS.sleep(1L); } catch (InterruptedException e) { e.printStackTrace(); }
	// 发送线程中断请求
	t1.interrupt();
	System.out.println("t1线程的中断标志位01 --> " + t1.isInterrupted()); // true
	try { TimeUnit.MILLISECONDS.sleep(500L); } catch (InterruptedException e) { e.printStackTrace(); }
	// 此时,t1 线程已经结束,回归默认状态
	System.out.println("t1线程的中断标志位03 --> " + t1.isInterrupted()); // false
}

 

 t1 线程执行完毕后,通过控制台的输出第二次获取线程的中断标识位,也时间 true。

当我们主线程休眠 500 ms 后,t1 线程已经执行完毕,生命周期结束,此时第三次获取 t1 线程的中断标识位,又变为了 false。验证了我们得出的结论:对于不活动的线程来说,中断请求默认为 false.

  • 验证阻塞状态下,调用中断方法抛出异常
public static void main(String[] args) {
	Thread t1 = new Thread(() -> {
		while (true) {
			if (Thread.currentThread().isInterrupted()) {
				System.out.println(Thread.currentThread().getName() + " 线程中断!!");
				break;
			}
			System.out.println("----> validate InterruptedException..");
			try {
				TimeUnit.MILLISECONDS.sleep(200);
			} catch (InterruptedException e) {
				// 抛出异常后,会将线程的中断标识清除,重置为默认状态,
				// 所以,如果不加这一行代码的话,会进行死循环,其他线程对 t1 线程发出的中断请求失败
				Thread.currentThread().interrupt();
				e.printStackTrace();
			}
		}
	}, "t1");
	t1.start();
	try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
	// 发送线程中断请求
	t1.interrupt();
}

  •  static boolean interrupted() 静态方法说明
public static void main(String[] args) {
	System.out.println(Thread.currentThread().getName() + " 线程中断状态01 ----> " + Thread.currentThread().isInterrupted());
	/**
	 * Thread.interrupted()  静态方法,做了两件事
	 * 1. 返回当前线程的中断状态,测试当前线程是否已被中断
	 * 2.将当前线程的中断状态清零并重新设置为 false,清除线程的中断状态
	 */
	Thread.currentThread().interrupt();
	System.out.println("====== 发送中断请求 =======");
	System.out.println(Thread.currentThread().getName() + " 线程中断状态02 ----> " + Thread.currentThread().isInterrupted());
	System.out.println(Thread.currentThread().getName() + " 线程中断状态03 ----> " + Thread.interrupted());
	System.out.println(Thread.currentThread().getName() + " 线程中断状态04 ----> " + Thread.currentThread().isInterrupted());
}

 来看一下源码:

 可以看出,静态方法和实例方法,其实都是调用了同一个 native 方法,只不过传入的参数不一样,一个是需要重置状态,一个是不需要。

相信通过上面的这些内容,童鞋们对中断线程都有了一些更深层次的认识了吧!

原网站

版权声明
本文为[搬运Gong]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_20315217/article/details/125573905