深入分析---虚拟线程VS传统多线程

目录

一、环境要求

二、为什么引进虚拟线程

三、创建虚拟线程的常用方法

1. 使用Thread.startVirtualThread()

结果

​编辑

2. 使用Thread.Builder

结果

3. 使用ExecutorService

4.使用ThreadFactory 

解释(参见注释)

四、虚拟线程&传统线程(代码比较)

1.创建10000个线程的对比

①传统多线程

解释

结果

 ②虚拟线程

结果

2.Web服务器请求处理对比

我们将关注下面四项指标

服务器端实现

①平台线程池服务器(8080)

②虚拟线程服务器(8085)

测试客户端实现

结果

五、虚拟线程的实现原理

1. 调度模型

2. 载体线程(Carrier Threads)

3. 栈管理

4. 挂起与恢复机制

六、虚拟线程的适用场景

1. 理想使用场景

2. 不适用场景

七、对比

什么叫"自动卸载"?

 比喻


在Java并发编程领域,虚拟线程(Virtual Threads)作为Java 19引入的预览特性并在Java 21中正式发布,代表了并发模型的一次重大革新。本文将深入探讨虚拟线程与传统平台线程(Platform Threads)的区别,通过代码示例展示它们的实现方式,分析性能差异,并详细解析虚拟线程的创建方法和底层原理。

一、环境要求

Idea版本:2024.x+

Jdk版本:21+

二、为什么引进虚拟线程

一句话总结:把“线程”变成和对象一样轻,可以随手开几十万甚至上百万条并发任务,而不用担心内存被栈吃光、CPU 被调度拖垮。

虚拟线程引进本来就是为了解决传统多线程的硬伤

  1. 太重:一个平台线程栈默认 1 MB,10 万条≈100 GB 内存,直接 OOM。

  2. 太少:操作系统线程是稀缺资源,几万条就把调度器压垮。

  3. 太贵:每次创建/销毁、上下文切换都要经过内核,CPU 时间浪费在调度而不是业务。

虚拟线程把“线程”变成 用户态对象+少量栈缓冲,创建/阻塞/切换都在 JVM 里完成,从而把“并发数量级”从 千级 提升到 百万级,同时保持与原有 Thread API 100 % 兼容。

三、创建虚拟线程的常用方法

1. 使用Thread.startVirtualThread()

Thread.startVirtualThread(() -> {
    System.out.println("Virtual thread running: " + Thread.currentThread());
});

【注】在 Lambda 表达式中,() -> { ... } 就是 Runnable 的简化写法,大括号里的代码就是线程要执行的逻辑。

所以上面的代码其实等价于:

Thread.startVirtualThread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Virtual thread running: " + Thread.currentThread());
    }
});
结果

同理,还等价于:

        // 定义一个Runnable任务
        Runnable task = () -> {
            // 在这里放置你的任务代码
            System.out.println("Virtual thread1 running...");
        };
        // 直接使用Thread类的静态方法startVirtualThread来创建并启动一个虚拟线程
        Thread vt =Thread.startVirtualThread(task);
        vt.join();

 上面三种表述都是一样的效果。

2. 使用Thread.Builder

Thread.Builder builder = Thread.ofVirtual().name("virtual-thread-", 1);
builder.start(() -> {
    System.out.println("Named virtual thread running");
});
结果

3. 使用ExecutorService

// 使用线程池创建虚拟线程
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        System.out.println("通过线程池创建的虚拟线程");
    });
} // 自动等待所有任务完成

【注】使用 ExecutorService(通过 Executors.newVirtualThreadPerTaskExecutor() 创建)配合 try-with-resources 语法,确实会自动等待所有提交的任务完成,无需手动调用 join() 方法。

try (资源声明1; 可选资源2; …) {
    // 使用资源的代码块
}

4.使用ThreadFactory 

ThreadFactory factory = Thread.ofVirtual().factory();
Thread virtualThread = factory.newThread(() -> {
    System.out.println("Virtual thread created via factory");
});
virtualThread.start();

解释(参见注释)

package com.qcby.vtTest;

import java.util.concurrent.ThreadFactory;

public class CreatVT {
    public static void main(String[] args) throws InterruptedException {
        /*
        * 让 JVM 返回一个“专门生产虚拟线程”的工厂对象。
          以后每调用一次 factory.newThread(task),就得到一条 尚未启动 的虚拟线程实例。
        * */
        ThreadFactory factory = Thread.ofVirtual().factory();
        /*
        * 把 Runnable 任务交给工厂,工厂把它包装成一条 新的虚拟线程对象 virtualThread。
          此时线程处于 NEW 状态,还没开始跑。
        * */
        Thread vt = factory.newThread(() -> {
            System.out.println("Virtual thread created via factory");
        });
        /*
        真正触发 JVM 把这条虚拟线程调度到 极少量 carrier 线程上运行。
        线程进入 RUNNABLE 状态,随后执行 System.out.println(...)
        * */
        vt.start();
        vt.join();
    }
}

【注】工厂模式只是把“创建线程”和“启动线程”解耦;底层仍是一条虚拟线程。

四、虚拟线程&传统线程(代码比较)

1.创建10000个线程的对比

①传统多线程

// 不推荐实际运行,可能导致系统资源耗尽
List<Thread> platformThreads = new ArrayList<>();
for (int i = 0; i < 10_000; i++) {
    Thread thread = new Thread(() -> { //循环10000次创建线程
        try {
            Thread.sleep(Duration.ofSeconds(1));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    thread.start();
    platformThreads.add(thread);
}

// 等待所有线程完成
for (Thread thread : platformThreads) {
    thread.join();
}
解释
  1. 向操作系统申请栈内存(默认 1 MB),合计 ≈ 10 GB;

  2. 在内核调度器里注册 10 000 个调度实体;

  3. 同时睡眠 1 秒,期间仍占着栈和调度资源。

结果
  • 内存瞬间暴涨,很可能触发 OutOfMemoryError

  • 操作系统线程调度开销激增,系统假死或崩溃。

【注】不要去运行,会导致“卡死”现象。

 ②虚拟线程

package com.qcby.vtTest;

import java.time.Duration;
import java.util.*;

public class CreatVT {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> virtualThreads = new ArrayList<>();
        int num=0;
        for (int i = 0; i < 10_000; i++) {

            Thread thread = Thread.startVirtualThread(() -> {
                try {
                    Thread.sleep(Duration.ofSeconds(1));
                    System.out.println(Thread.currentThread().threadId()+"创建成功");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            virtualThreads.add(thread);
        }

        // 等待所有线程完成
        for (Thread thread : virtualThreads) {
            thread.join();
        }
    }
}
结果

2.Web服务器请求处理对比

Web 服务器请求处理对比,就是把“用传统线程池(Tomcat/Jetty 默认)” 与 “用虚拟线程” 两种方案,放在同一个 Web 服务器场景下,从 并发量、延迟、资源占用 等维度做 A/B 比较。

Web 服务器的场景里,“请求处理” ≈ 并发能力

• 并发量直接决定能同时服务多少用户;
• 并发模型(线程池 vs 虚拟线程)直接决定内存、上下文切换、延迟。

我们将关注下面四项指标

  1. 吞吐量(Requests/Second)

  2. 延迟(Latency)

  3. 资源利用率(CPU、内存)

  4. 线程数(活跃线程/虚拟线程挂载数)

服务器端实现

平台线程池服务器(8080)
import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class ThreadPoolServer {
    private static final int THREAD_POOL_SIZE = 200;
    private static final ExecutorService executor = 
        Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    
    public static void main(String[] args) throws IOException {
        int port = 8080;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("ThreadPool Server started on port " + port);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                handleRequest(clientSocket);
            }
        }
    }
    
    private static void handleRequest(Socket socket) {
        executor.execute(() -> {
            try {
                // 模拟I/O操作
                InputStream input = socket.getInputStream();
                byte[] buffer = new byte[1024];
                int read = input.read(buffer);
                
                // 处理请求
                Thread.sleep(100); // 模拟业务处理时间
                
                // 发送响应
                OutputStream output = socket.getOutputStream();
                output.write("HTTP/1.1 200 OK\r\n\r\nHello from ThreadPool".getBytes());
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}
虚拟线程服务器(8085)
import java.io.*;
import java.net.*;

public class VirtualThreadServer {
    public static void main(String[] args) throws IOException {
        int port = 8085;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("VirtualThread Server started on port " + port);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                handleRequest(clientSocket);
            }
        }
    }
    
    private static void handleRequest(Socket socket) {
        Thread.startVirtualThread(() -> {
            try {
                // 模拟I/O操作
                InputStream input = socket.getInputStream();
                byte[] buffer = new byte[1024];
                int read = input.read(buffer);
                
                // 处理请求
                Thread.sleep(100); // 模拟业务处理时间
                
                // 发送响应
                OutputStream output = socket.getOutputStream();
                output.write("HTTP/1.1 200 OK\r\n\r\nHello from VirtualThread".getBytes());
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

测试客户端实现

import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class LoadTestClient {
    private static final int TOTAL_REQUESTS = 10_000;
    private static final int CONCURRENT_CLIENTS = 1_000;
    
    public static void main(String[] args) throws InterruptedException {
        // 测试线程池服务器
        testServer("localhost", 8080, "ThreadPool Server");
        
        // 测试虚拟线程服务器
        testServer("localhost", 8085, "VirtualThread Server");
    }
    
    private static void testServer(String host, int port, String serverName) 
            throws InterruptedException {
        final AtomicInteger completedRequests = new AtomicInteger();
        final AtomicInteger failedRequests = new AtomicInteger();
        final CountDownLatch latch = new CountDownLatch(CONCURRENT_CLIENTS);
        
        long startTime = System.currentTimeMillis();
        
        ExecutorService clientExecutor = Executors.newFixedThreadPool(CONCURRENT_CLIENTS);
        
        for (int i = 0; i < CONCURRENT_CLIENTS; i++) {
            clientExecutor.execute(() -> {
                for (int j = 0; j < TOTAL_REQUESTS / CONCURRENT_CLIENTS; j++) {
                    try (Socket socket = new Socket(host, port);
                         OutputStream out = socket.getOutputStream();
                         InputStream in = socket.getInputStream()) {
                        
                        // 发送简单HTTP请求
                        String request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
                        out.write(request.getBytes());
                        out.flush();
                        
                        // 读取响应
                        byte[] response = new byte[1024];
                        int bytesRead = in.read(response);
                        
                        completedRequests.incrementAndGet();
                    } catch (Exception e) {
                        failedRequests.incrementAndGet();
                    }
                }
                latch.countDown();
            });
        }
        
        latch.await();
        clientExecutor.shutdown();
        
        long endTime = System.currentTimeMillis();
        long totalTime = endTime - startTime;
        
        System.out.println("\n" + serverName + " Test Results:");
        System.out.println("Total requests: " + TOTAL_REQUESTS);
        System.out.println("Completed requests: " + completedRequests.get());
        System.out.println("Failed requests: " + failedRequests.get());
        System.out.println("Total time: " + totalTime + " ms");
        System.out.println("Throughput: " + 
            (completedRequests.get() / (totalTime / 1000.0)) + " requests/sec");
    }
}
结果

五、虚拟线程的实现原理

1. 调度模型

虚拟线程采用M:N调度模型:

  • M个虚拟线程映射到N个平台线程(N通常等于CPU核心数)

  • JVM负责虚拟线程的调度,而非操作系统

  • 当虚拟线程执行阻塞操作时,JVM会自动将其挂起,释放平台线程

【注】平台线程 = OS 线程 = 传统意义上的 Java 线程(Thread 或线程池里的工作线程)

  • 平台 = 操作系统(Windows、Linux、macOS)。

  • 平台线程 = 由操作系统直接创建、调度的线程。

2. 载体线程(Carrier Threads)

虚拟线程运行在平台线程(称为载体线程)上:

  • 挂起的虚拟线程不占用载体线程

  • 一个载体线程可以依次执行多个虚拟线程

  • JVM维护一个ForkJoinPool作为虚拟线程的默认调度器

3. 栈管理

虚拟线程使用"栈切片"技术:

  • 栈空间按需分配和释放

  • 挂起时保存栈状态,恢复时重建

  • 避免了固定大栈的内存浪费

4. 挂起与恢复机制

当虚拟线程遇到阻塞操作时:

  1. 当前状态(包括栈帧和局部变量)被保存到堆内存

  2. 载体线程被释放以执行其他虚拟线程

  3. 当I/O操作完成时,虚拟线程被重新调度到可用载体线程

  4. 之前的状态被恢复,执行继续

六、虚拟线程的适用场景

1. 理想使用场景

  • 高并发I/O密集型应用(如Web服务器)

  • 需要处理大量并发连接

  • 包含许多阻塞操作的任务

  • 需要简单同步代码但又要高性能的场景

2. 不适用场景

  • CPU密集型任务(无性能优势)

  • 需要线程本地存储的重度使用

  • 依赖线程优先级或精细线程控制的场景

七、对比

平台线程 = 传统 Java 线程 = 操作系统线程;

虚拟线程 = JVM 在“用户空间”里模拟的“轻量级线程”

特性平台线程(传统线程)虚拟线程(Virtual Thread)
由谁创建操作系统JVM
数量上限几千个百万级
内存占用约 1 MB/线程几百字节起
切换成本内核态切换,较重纯用户态切换,极轻
是否会被阻塞可以自动卸载(前提:不被 synchronized 或 JNI 钉住)

什么叫"自动卸载"?

  • 虚拟线程在执行 阻塞 I/O(如 socket.read()时,理论上会把当前任务从平台线程上“摘下来”,让这个平台线程去干别的活。

  • 这个过程叫 unmount(卸载)。

  • 等 I/O 就绪后,再随便找一个空闲的平台线程把虚拟线程“挂回去”继续跑。

 比喻

  • 正常情况:虚拟线程像“乘客”,平台线程像“出租车”。
    乘客打车去商场,路上堵车(阻塞 I/O)→ 乘客下车,出租车去接别的单。

  • 被钉住:乘客上车后把自己焊死在座位上(进入 synchronized),出租车只能一直等他,别人打不了这辆出租车。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值