随时声明线程 和 jvm钩子的使用

本文介绍两种创建Java线程的方法:通过匿名内部类实现Runnable接口和使用JVM钩子。JVM钩子允许在JVM关闭时执行指定任务,文中提供了一个简单的示例,展示了如何注册和使用JVM钩子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般使用线程都想需要实现Runnable接口或继承Thread类,但是有时我们没必要去声明一个类来达到使用线程的目的 ,随时随地声明你的线程。code below:

 public void generateThread()
  {
      Thread tr = new Thread()
      {
          public void run()
          {
             while(true)
            {
               System.out.println("afsdf");
            }
         }
     };

tr.start();//start the thread
  }

 

jvm钩子的使用:首先需要向jvm注册需要运行的类,必须是一个线程类,当jvm关闭时按照想虚拟机注册的顺序来调用那些已经注册的线程类,可参考javaapi
 
首先实现一个线程类,例子代码
package cn.com;

public class ShutDownHookTest implements Runnable {

 
 public void run() {
       System.out.println("hahah i am running");  
 }
 
}
其次想jvm注册该线程类
package cn.com;

public class ShutdownApp {

  public static void main(String[] args)
  {
      Runtime rt = Runtime.getRuntime();
      rt.addShutdownHook(new Thread(new ShutDownHookTest()));//register to the jvm
  }
}
最后运行程序,会输出hahah i am running,表明钩子已生效

 

<think>我们刚刚详细讨论了Project Loom虚拟线程,现在用户问的是“Java JVM是怎么调度虚拟线程的”。这个问题其实在之前的讲解中已经涵盖了核心部分,但需要更聚焦在调度机制上。 根据之前的回答,虚拟线程的调度核心是M:N调度模型,由JVM的调度器负责将虚拟线程(M个)映射到载体线程(N个)上执行。关键点包括: 1. 调度器:JVM内部的调度器(ForkJoinPool的变体?),不是OS内核调度器。 2. 挂起与恢复:当虚拟线程执行阻塞操作时,JVM会将其挂起(保存栈状态到堆),并释放载体线程;当阻塞操作完成,调度器会安排它在可用的载体线程上恢复执行。 3. 协作式调度:调度发生在特定的阻塞点(I/O、锁等),而不是像OS线程那样抢占式。 现在需要更详细地展开调度过程,并确保回答完整。同时,最后必须按照要求提出3-5个相关问题。 注意:用户要求用中文回答,且最后必须以“§§相关问题§§”开头的问题列表,不能换行分散。 因此,回答结构: 1. 详细解释调度机制(包括调度器、挂起恢复、协作式等) 2. 最后输出“§§相关问题§§”并列出3-5个相关问题 由于用户要求30分钟深度的知识,虽然这个问题是其中一部分,但可以独立成篇,所以需要详细。</think>Java JVM 调度虚拟线程的核心机制是 **M:N 协作式调度模型**,其设计目标是高效处理海量轻量级线程的阻塞操作。以下是详细调度原理: --- ### **一、调度架构:M:N 模型** 1. **M(虚拟线程)** - 由 JVM 管理的轻量级线程,生命周期内不绑定特定 CPU 核心。 - 初始栈仅占用数百字节堆内存(传统线程需 1MB 栈空间)。 2. **N(载体线程 - Carrier Threads)** - 实际执行代码的操作系统线程(平台线程),数量通常等于 CPU 核心数。 - 组成固定大小的线程池(如 `ForkJoinPool`),称为 **载体线程池**。 3. **映射关系** - 调度器动态将虚拟线程分配到空闲载体线程上执行。 - 虚拟线程与载体线程是临时绑定关系(非固定绑定)。 --- ### **二、调度过程详解** #### **步骤 1:虚拟线程启动** ```java Thread.startVirtualThread(() -> { // 任务逻辑(含阻塞操作) }); ``` - JVM 分配虚拟线程对象,将其加入 **就绪队列**。 #### **步骤 2:绑定载体线程** - 调度器从载体线程池选取空闲线程。 - 虚拟线程栈状态(调用栈/局部变量)复制到载体线程栈中。 - **关键特性**:绑定过程仅复制必要数据(非完整栈拷贝),开销极低。 #### **步骤 3:执行与阻塞检测** 当虚拟线程执行到 **阻塞点** 时: ```java socket.read(); // I/O 阻塞 lock.lock(); // 锁竞争 Thread.sleep(); // 定时阻塞 ``` - JVM 通过 **修改字节码** 或 **JNI 钩子** 检测阻塞操作。 - 触发调度器介入。 #### **步骤 4:挂起虚拟线程** 1. **保存执行状态** - 通过 **Continuation(延续体)** 捕获当前栈帧状态(保存到堆内存)。 2. **释放载体线程** - 虚拟线程标记为 `BLOCKED`,载体线程立即返回到线程池。 3. **注册唤醒事件** - 向操作系统注册 I/O 完成事件(如 `epoll`/`kqueue`)或锁可用通知。 #### **步骤 5:恢复执行** - 当阻塞条件满足(如数据到达、锁释放): 1. 事件通知 JVM 调度器。 2. 虚拟线程状态改为 `RUNNABLE`,重新加入就绪队列。 3. 调度器分配 **任意空闲载体线程** 恢复执行(不一定是原载体线程)。 --- ### **三、调度器核心特性** 1. **协作式(非抢占)** - 仅在 **已知阻塞点**(I/O、锁等)触发调度,无时间片轮转。 - 避免不必要的上下文切换(传统线程切换需 1-10μs,虚拟线程仅 0.1μs)。 2. **工作窃取(Work-Stealing)** - 载体线程空闲时,从其他线程队列偷取任务(基于 `ForkJoinPool` 算法)。 3. **优先级继承** - 载体线程继承虚拟线程优先级(通过 `Thread.setPriority()` 设置)。 4. **无饥饿保障** - 虚拟线程可能因载体线程不足而延迟(需合理设置载体线程池大小)。 --- ### **四、与传统线程调度对比** | **特性** | 虚拟线程调度 | 平台线程调度 | |------------------|----------------------------|--------------------------| | **调度主体** | JVM 用户态调度器 | 操作系统内核 | | **切换开销** | 保存/恢复堆栈帧(纳秒级) | 内核态切换(微秒级) | | **阻塞代价** | 释放载体线程(近乎零成本) | 线程休眠+唤醒(高成本) | | **调度时机** | 显式阻塞点 | 时间片耗尽或阻塞 | | **并发量上限** | 百万级(受堆内存限制) | 数千(受内核限制) | --- ### **五、性能优化机制** 1. **载体线程池调优** - 默认大小 = CPU 核心数(可通过 `jdk.virtualThreadScheduler.parallelism` 调整)。 - 计算密集型任务需增大载体线程数。 2. **避免 "Pinning" 问题** - 在 `synchronized` 块内阻塞会固定载体线程(需改用 `ReentrantLock`)。 3. **Continuation 压缩** - JVM 自动合并相邻栈帧,减少状态保存开销(如尾递归优化)。 --- ### **六、调度示例场景** 假设 3 个虚拟线程(VT1~3) 2 个载体线程(CT1~2): 1. VT1 在 CT1 执行 → 调用 `socket.read()` 阻塞 → VT1 挂起,CT1 释放。 2. VT2 绑定 CT1 执行 → 无阻塞快速完成 → CT1 空闲。 3. VT3 绑定 CT2 执行 → 调用 `Thread.sleep(100)` → VT3 挂起,CT2 释放。 4. Socket 数据到达 → VT1 恢复,绑定 CT1 继续执行。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值