一题带你彻底理解 sleep() 和 wait()

1

一道Java的题目:

关于sleep()和wait(),以下描述错误的一项是:

–Asleep是线程类(Thread)的方法,wait是Object类的方法;

–Bsleep不释放对象锁,wait放弃对象锁

–Csleep暂停线程、但监控状态仍然保持,结束后会自动恢复

–Dwait后进入等待锁定池,只有针对此对象发出notify方法后获得对象锁进入运行状态

2

sleep()不会释放掉锁(监控)

答案是D,不明白的可以继续往下看。

3

关于对象锁:

截取网上的一段话:

 所有对象都自动含有单一的锁。

JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候,计数变为1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增。

只有首先获得锁的任务(线程)才能继续获取该对象上的多个锁。

每当任务离开一个synchronized(同步)方法,计数递减,当计数为0的时候,锁被完全释放,此时别的任务就可以使用此资源。

这段话令人感到迷惑,一个对象不是只有一个锁吗?只有获得这个对象的锁才能对它进行操作,若这个对象的锁被一个线程先获得,那就其他线程就需要等待。那多次加锁什么意思,锁不是依附于对象的吗?

在往下的文章中,我暂且理解为一个对象有且只有一把锁,锁在不同线程间传递,一个线程可以多次获得同一个对象的锁。暂且不考虑一个对象上多个锁这种方法是不是确实存在,这对下面影响不大。

4

关于锁池和等待池

在Java中,每个对象都有两个池,锁(monitor)池和等待池

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

深入理解:

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

注:wait() ,notifyAll(),notify() 三个方法都是Object类中的方法.

5

关于wait() ,notifyAll(),notify() 三个方法

wait()

public final void wait()

throws InterruptedException,IllegalMonitorStateException

该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方法。进入 wait()方法后,当前线程释放锁。在从 wait()返回前,线程与其他线程竞争重新获得锁。如果调用 wait()时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。

notify()

public final native void notify()

throws IllegalMonitorStateException

该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify()时没有持有适当的锁,也会抛出 IllegalMonitorStateException。

该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notifyAll。这里需要注意:它们等待的是被 notify 或 notifyAll,而不是锁。这与下面的 notifyAll()方法执行后的情况不同。

notifyAll()

public final native void notifyAll()

throws IllegalMonitorStateException

该方法与 notify ()方法的工作方式相同,重要的一点差异是:

notifyAll 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待 notify 或 notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll 线程退出调用了 notifyAll 的 synchronized 代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

扩展阅读

深入理解正则表达式

彻底理解SpringIOC、DI-这篇文章就够了

涨姿势,图文带你了解 8 大排序算法

来源:https://blog.csdn.net/u014561933/article/details/58639411

微信公众号:javafirst

扫码关注免费获取更多资源 

<think>首先,用户的问题是:“我想在Java中终止正在运行的线程 请问Java 如何安全地终止一个正在运行的线程”。我需要基于系统级指令用户可见层的要求来构建我的回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用[1]: Java 中如何终止一个正在运行的线程。所以,我们可以知道,Java 虚拟机(JVM)作为用户态程序,其实是无法直接终止一个线程的。当我们调用thread.stop()这类方法时,实际上是在用户态层面发出中断请求,而真正的线程终止还需要操作系统内核的配合。[^1] - 引用[2]: 如何安全的结束一个正在运行的线程。java.lang.Thread类包含了一些常用的方法,如:start(), stop(), stop(Throwable) ,suspend(), destroy(),resume()。通过这些方法,我们可以对线程进行方便的操作,但是这些方法中,只有start()方法得到了保留。[^2] 这些引用表明,使用Thread.stop()等方法是不安全的,已被弃用。安全终止线程的方法包括使用标志位或中断机制。 关键点: - Thread.stop() 不安全,因为它可能导致资源泄漏或数据不一致。 - 推荐方法是使用interrupt()方法检查中断状态。 - 或者使用一个volatile布尔标志来控制线程运行。 我的回答应该结构清晰: 1. 解释问题:为什么Thread.stop()不安全。 2. 提供安全方法:使用中断或标志位。 3. 给出代码示例。 4. 添加相关问题。 使用中文回答。 生成相关问题:在最后添加相关问题部分。 数学表达式:用户的问题是关于Java线程的,不涉及数学,所以可能不需要数学表达式。但如果提到任何概念如时间或概率,可以适当添加,但这里可能不需要。确保如果有数学,使用正确格式。 引用:在回答中自然地添加引用标识。例如,提到Thread.stop()不安全时,引用[^1]或[^2]。 结构化回答: - 引言:解释终止线程的需求。 - 问题:为什么直接终止不安全。 - 解决方案:安全方法。 - 代码示例。 - 总结。 - 相关问题。 代码示例:使用Java代码来展示安全终止。 相关问题: 1. 如何检查线程的中断状态? 2. 什么是volatile关键字的作用? 3. 在哪些情况下线程不能被中断? 4. 如何优雅地处理线程中断? 5. Java线程池如何管理线程终止? 确保回答真实可靠:基于Java官方文档最佳实践。</think>### Java 中安全终止正在运行的线程 在 Java 中,安全地终止一个正在运行的线程是一个常见需求。直接使用 `Thread.stop()` 等方法是不推荐的,因为它可能导致资源泄漏、数据不一致或程序崩溃。例如,`Thread.stop()` 会强制终止线程,而不给线程清理资源的机会,这违反了 Java 的安全性原则[^1]。实际上,Java 虚拟机 (JVM) 作为用户态程序,无法直接终止线程;真正的线程终止需要操作系统内核的配合,但用户态代码应通过协作机制来实现安全终止[^1]。 以下是安全终止线程的推荐方法,我将逐步解释并给出代码示例。 #### 步骤 1: 理解为什么需要安全终止 - **不安全方法的风险**:如 `Thread.stop()`、`Thread.suspend()` 等方法已被弃用(deprecated),因为它们会突然中断线程执行,可能导致锁未释放或对象状态损坏[^2]。只有 `Thread.start()` 是安全保留的方法。 - **安全原则**:线程应在可控条件下自行结束,例如通过检查中断标志或自定义标志位。这允许线程在终止前执行清理操作(如关闭文件或释放锁)。 #### 步骤 2: 使用中断机制(推荐方法) Java 提供了 `Thread.interrupt()` 方法,它不会强制终止线程,而是设置一个中断标志。线程需要定期检查这个标志,并在检测到中断时优雅退出。 - **关键方法**: - `interrupt()`:向线程发送中断请求。 - `isInterrupted()` 或 `Thread.interrupted()`:检查中断状态(后者会清除中断标志)。 - 在阻塞操作(如 `sleep()` 或 `wait()`)中,中断会抛出 `InterruptedException`,线程应捕获此异常并退出。 **代码示例**: ```java public class SafeThreadTermination { public static void main(String[] args) throws InterruptedException { // 创建一个线程 Thread workerThread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { // 模拟工作:例如读取数据或计算 System.out.println("线程正在运行..."); Thread.sleep(1000); // 阻塞操作,可被中断 } catch (InterruptedException e) { // 捕获中断异常,退出循环 System.out.println("线程被中断,正在清理资源..."); Thread.currentThread().interrupt(); // 重新设置中断标志 break; } } System.out.println("线程安全终止"); }); workerThread.start(); Thread.sleep(3000); // 主线程等待 3 秒 workerThread.interrupt(); // 安全发送中断请求 } } ``` - **解释**:线程在循环中检查 `isInterrupted()`。当主线程调用 `interrupt()` 时,工作线程的 `sleep()` 会抛出 `InterruptedException`,线程捕获异常后退出循环,并执行清理代码。这确保了资源释放。 #### 步骤 3: 使用自定义标志位(替代方法) 如果线程不涉及阻塞操作,可以使用一个 `volatile` 布尔变量作为标志位。`volatile` 确保多线程间的可见性(即一个线程修改标志,其他线程立即可见)。 **代码示例**: ```java public class SafeThreadWithFlag { private volatile boolean running = true; // volatile 保证可见性 public void stopThread() { running = false; // 设置标志位,通知线程停止 } public void startThread() { Thread workerThread = new Thread(() -> { while (running) { // 模拟工作:例如计算或数据处理 System.out.println("线程工作中..."); try { Thread.sleep(500); // 非必要,仅为示例 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 处理潜在中断 } } System.out.println("线程安全终止"); }); workerThread.start(); } public static void main(String[] args) throws InterruptedException { SafeThreadWithFlag example = new SafeThreadWithFlag(); example.startThread(); Thread.sleep(2000); // 主线程等待 2 秒 example.stopThread(); // 安全终止线程 } } ``` - **解释**:`running` 标志位被声明为 `volatile`,确保工作线程能及时检测到变化。当 `stopThread()` 被调用时,线程退出循环并终止。 #### 注意事项 1. **阻塞操作的处理**:如果线程在 `wait()` 或 I/O 操作中阻塞,优先使用中断机制,因为标志位可能无法立即生效。 2. **资源清理**:在终止前,确保关闭文件、数据库连接或释放锁,避免内存泄漏。 3. **JVM 限制**:Java 无法强制终止线程,只能通过协作机制。如果线程不响应中断(如陷入死循环),可能需要外部干预(如超时机制)[^1]。 4. **现代替代方案**:在 Java 并发包(`java.util.concurrent`)中,使用 `ExecutorService` `Future.cancel()` 可以更优雅地管理线程池中断。 安全终止线程的核心是“协作式”设计:线程应主动检查终止条件,而不是被外部强行停止。这符合 Java 的并发最佳实践[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值