当前位置: 首页 > news >正文

底层源码和具体测试解析HotSpot JVM的notify唤醒有序性(5000字详解)

          在大家的认知里,或者大家也可能搜过,notify唤醒机制到底是随机的呢?还是顺序的呢?在网上其实也有很多人说notify的唤醒机制就是随机的,但实际上并不是这样的,notify的唤醒机制是先进先出的!

目录

源码注释说明

具体测试用例

测试用例一

测试用例二

底层原理解析

ObjectMonitor 的数据结构

wait源码

notify源码

 DequeueWaiter源码

  源码结论     


源码注释说明

让我们先来看看源码注释是怎么写的

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.

翻译为:

唤醒正在等待此对象的监视器的单个线程。如果任何线程正在等待此对象,则选择其中一个线程进行唤醒。选择是任意的,由实现自行决定。线程通过调用其中一个 wait 方法等待对象的监视器。

      在注释中其实就已经说明了,选择是任意的,由实现自行决定的,既然注释都这样写了,那我们就先用测试用例测试一下。

具体测试用例

测试用例一

public class Main {private static final Object lock = new Object();private static List<String> awakingOrder = Collections.synchronizedList(new ArrayList<>());private static final int THREAD_COUNT = 100;private static CountDownLatch readyLatch = new CountDownLatch(THREAD_COUNT);private static CountDownLatch startLatch = new CountDownLatch(1);private static CountDownLatch finishLatch = new CountDownLatch(THREAD_COUNT);static class WaitingThread extends Thread {private String name;public WaitingThread(String name) {this.name = name;}@Overridepublic void run() {try {// 先通知主线程该线程已准备就绪readyLatch.countDown();// 等待主线程发出开始信号startLatch.await();synchronized (lock) {System.out.println(name + " 准备等待");lock.wait();awakingOrder.add(name);System.out.println(name + " 被唤醒");}} catch (InterruptedException e) {e.printStackTrace();} finally {finishLatch.countDown();}}}public static void main(String[] args) throws InterruptedException {List<WaitingThread> threads = new ArrayList<>();// 创建并启动100个线程for (int i = 1; i <= THREAD_COUNT; i++) {WaitingThread thread = new WaitingThread("线程" + i);threads.add(thread);thread.start();}// 等待所有线程准备就绪readyLatch.await();System.out.println("所有线程已准备就绪");// 发出开始信号,让所有线程开始等待startLatch.countDown();// 确保所有线程都进入等待状态Thread.sleep(1000);// 依次唤醒所有线程for (int i = 0; i < THREAD_COUNT; i++) {synchronized (lock) {System.out.println("开始第 " + (i + 1) + " 次唤醒");lock.notify();}// 给一点时间让被唤醒的线程执行完毕Thread.sleep(10);}// 等待所有线程执行完毕finishLatch.await();// 输出唤醒顺序System.out.println("唤醒顺序: " + awakingOrder);System.out.println("唤醒的线程总数: " + awakingOrder.size());}
}

       测试用例代码如上,我一开始看的时候还在想HotSpot JVM的的notify唤醒机制原来真的是顺序唤醒的,但是我再仔细看的时候发现并不是这样的!

        当我再往上看的时候发现,原来因为线程并发执行的原因导致并不是按照我原先设置的顺序获取到了synchronized锁!而导致了这个原因,经过对比其实可以发现,线程获取到锁等待的顺序是和被唤醒的顺序是一致的!所以HotSpot JVM的notify实现是先进先出的有序队列。

测试用例二

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class Main {private static final Object lock = new Object();private static List<String> awakingOrder = Collections.synchronizedList(new ArrayList<>());private static final int THREAD_COUNT = 100;private static volatile int currentThreadIndex = 0;static class WaitingThread extends Thread {private String name;private int index;private CountDownLatch prevLatch;private CountDownLatch myLatch;public WaitingThread(String name, int index, CountDownLatch prevLatch, CountDownLatch myLatch) {this.name = name;this.index = index;this.prevLatch = prevLatch;this.myLatch = myLatch;}@Overridepublic void run() {try {// 等待前一个线程就绪if (prevLatch != null) {prevLatch.await();}synchronized (lock) {System.out.println(name + " 准备等待,序号: " + index);// 通知下一个线程可以开始等待if (myLatch != null) {myLatch.countDown();}lock.wait();awakingOrder.add(name);System.out.println(name + " 被唤醒,序号: " + index);}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {List<WaitingThread> threads = new ArrayList<>();List<CountDownLatch> latches = new ArrayList<>();// 创建用于线程间同步的CountDownLatch数组for (int i = 0; i < THREAD_COUNT; i++) {latches.add(new CountDownLatch(1));}// 创建并启动线程for (int i = 0; i < THREAD_COUNT; i++) {CountDownLatch prevLatch = i == 0 ? null : latches.get(i - 1);CountDownLatch myLatch = latches.get(i);WaitingThread thread = new WaitingThread("线程" + (i + 1), i + 1, prevLatch, myLatch);threads.add(thread);thread.start();}// 等待一段时间确保所有线程都进入等待状态Thread.sleep(2000);System.out.println("开始按顺序唤醒线程");// 按顺序唤醒线程for (int i = 0; i < THREAD_COUNT; i++) {synchronized (lock) {System.out.println("准备唤醒第 " + (i + 1) + " 个线程");lock.notify();}// 给足够的时间让被唤醒的线程完成执行Thread.sleep(50);}// 等待所有线程完成for (Thread thread : threads) {thread.join();}// 输出唤醒顺序System.out.println("唤醒顺序: " + awakingOrder);System.out.println("唤醒的线程总数: " + awakingOrder.size());// 验证唤醒顺序是否正确boolean isOrdered = true;for (int i = 0; i < awakingOrder.size(); i++) {String expectedThread = "线程" + (i + 1);if (!awakingOrder.get(i).equals(expectedThread)) {isOrdered = false;break;}}System.out.println("唤醒顺序是否正确: " + isOrdered);}
}

       修改原来的代码 ,为每个线程创建一个 CountDownLatch,每个线程需要等待前一个线程进入等待状态才能继续,使用 prevLatch 和 myLatch 确保线程按顺序进入等待状态,这样就可以顺序的进入锁了,让我们再看下运行结果

 这样其实就可以清晰的看到这个唤醒的结果了,就是先进先出,顺序!

底层原理解析

现在让我们深入分析 HotSpot JVM 中的实现原理

ObjectMonitor 的数据结构

ObjectMonitor 的数据结构在 HotSpot 中,synchronized 的实现依赖于 ObjectMonitor 类,其结构如下:

ObjectMonitor() {_header       = NULL;_count        = 0;_waiters      = 0;_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL;    // 等待线程队列_WaitSetLock  = 0;_Responsible  = NULL;_succ         = NULL;_cxq          = NULL;FreeNext      = NULL;_EntryList    = NULL;    // 待竞争线程队列_SpinFreq     = 0;_SpinClock    = 0;
}

wait源码

让我们看看具体的wait等待实现

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {// 将当前线程封装成 ObjectWaiter 对象ObjectWaiter node(THREAD);node.TState = ObjectWaiter::TS_WAIT;// 加入等待队列AddWaiter(&node);// 释放对象锁exit(true, THREAD);// 等待被唤醒Thread::SpinWait(node);
}

notify源码

再看看notify的唤醒实现 

void ObjectMonitor::notify(TRAPS) {// DequeueWaiter 方法会从 _WaitSet 队列头部取出一个等待线程ObjectWaiter* waiter = DequeueWaiter();if (waiter != NULL) {// 将等待线程移动到 EntryListExitExit(waiter);}
}

 DequeueWaiter源码

ObjectWaiter* ObjectMonitor::DequeueWaiter() {// 从 _WaitSet 队列头部获取第一个等待线程ObjectWaiter* waiter = _WaitSet;if (waiter != NULL) {_WaitSet = waiter->_next;waiter->_prev = NULL;waiter->_next = NULL;}return waiter;
}

  源码结论     

         从源码可以看出,wait() 的线程被放入 WaitSet 队列,notify() 总是唤醒 WaitSet 队列中的第一个线程,虽然 Java 规范说 notify 的选择是随机的,但在 HotSpot 实现中实际上是 FIFO(先进先出)的

      运行上面的测试代码,你也会发现,线程被唤醒的顺序与它们进入等待状态的顺序是一致的,多次运行结果都是一样的(在相同的 JVM 实现下),这验证了在 HotSpot JVM 中,notify() 确实是按照 FIFO 顺序唤醒线程的

      但需要注意的是:这个行为是 HotSpot JVM 的具体实现,其他 JVM 可能会有不同的实现

 

http://www.xdnf.cn/news/178309.html

相关文章:

  • 优化无头浏览器流量:使用Puppeteer进行高效数据抓取的成本降低策略
  • PHP实现 Apple ID 登录的服务端验证指南
  • 现代Python打包工具链
  • Redis的阻塞
  • AI赋能智能对讲机:技术融合与行业变革的深度实践
  • 【计算机网络性能优化】从基础理论到实战调优
  • 97A6-ASEMI无人机专用功率器件97A6
  • (25)VTK C++开发示例 --- 将点坐标写入.xyz文件
  • Java基础 — 数组
  • Spark-Streaming(四)
  • 天梯——现代战争
  • NTFS和EXFAT哪个好:深入解析这两种文件系统的优劣
  • FAQ运用
  • 在使用docker创建容器运行报错no main manifest attribute, in app.jar
  • springboot logback 默认加载配置文件顺序
  • Leetcode:283. 移动零
  • 【大模型微调与应用开发实战指南】从理论到工业级部署
  • COMSOL多孔介质自然对流与传热现象的仿真研究
  • 《原神/星穹铁道私服怎么建?内网穿透+本地调试完整指南》
  • 【Vue】单元测试(Jest/Vue Test Utils)
  • 高德地图 API 拿到当前定位和目的地址转经纬度,实现路径规划
  • django filter 排除字段
  • C++学习:六个月从基础到就业——模板编程:类模板
  • 淘宝tb.cn短链接生成
  • 基于ruoyi-plus实现AI聊天和绘画
  • 前端面试 js
  • 考研系列-计算机组成原理第六章、总线
  • 国标GB28181视频平台EasyCVR助力打造太阳能供电远程视频监控系统
  • 2025系统架构师---数据库架构风格
  • 多模态大语言模型arxiv论文略读(四十四)