CodeGym /课程 /JAVA 25 SELF /创建虚拟线程:Thread.ofVirtual().start()

创建虚拟线程:Thread.ofVirtual().start()

JAVA 25 SELF
第 57 级 , 课程 1
可用

1. 如何在实践中创建虚拟线程

该从理论转向实践了!你已经知道传统方式如何创建线程:

Thread t = new Thread(() -> System.out.println("Hello from thread!"));
t.start();

或者更简洁:

new Thread(() -> System.out.println("Hi!")).start();

现在在 Java 21 中,我们有了新的方式:

Thread.startVirtualThread(() -> System.out.println("Hello from virtual thread!"));

或者更显式地:

Thread t = Thread.ofVirtual().start(() -> System.out.println("Hello from virtual thread!"));

有什么区别?

  • Thread.ofVirtual().start(...) 创建 虚拟线程(Virtual Thread),由 JVM 管理,而不是由操作系统管理。
  • Thread.ofPlatform().start(...)(或 new Thread(...))——像以前一样的经典平台线程。

为什么这很重要?

虚拟线程可以按数万的规模创建,而不必担心 OutOfMemoryError。现在,如果你突然想处理上百万个请求——Java 会说:“没问题,再来点!”

2. 创建虚拟线程的语法

基础示例:

public class VirtualThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.ofVirtual().start(() -> {
            System.out.println("来自虚拟线程的问候!线程: " + Thread.currentThread());
        });
        // 等待线程结束(避免 main 提前退出)
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

发生了什么?

  • 我们通过 Thread.ofVirtual().start(...) 创建一个虚拟线程。
  • 在线程内部执行一个简单动作:打印一条消息。
  • 最后调用 thread.join(),让主线程等待虚拟线程结束(否则程序可能在它来得及输出之前就结束)。

请注意:
虚拟线程看起来并且行为上几乎与普通线程一致,但其内部是 JVM 的“魔法”!

3. 批量创建虚拟线程:Loom 的威力实战

现在来尝试一件对普通线程而言很冒险(或者几乎不可能)的事:创建 10_000 个虚拟线程,每个线程打印自己的编号。

public class VirtualThreadMassive {
    public static void main(String[] args) throws InterruptedException {
        int N = 10_000;
        Thread[] threads = new Thread[N];

        for (int i = 0; i < N; i++) {
            int threadNum = i;
            threads[i] = Thread.ofVirtual().start(() -> {
                System.out.println("虚拟线程 #" + threadNum + " 正在工作!");
            });
        }

        // 等待所有线程结束
        for (Thread t : threads) {
            t.join();
        }
        System.out.println("所有虚拟线程都已结束!");
    }
}
  • 对于普通线程(new Thread(...)),这样的代码几乎肯定会因 OutOfMemoryError 让你的程序“崩掉”。
  • 而对虚拟线程来说——这就是常态!JVM 可以轻松处理成千上万甚至数以万计的线程。

顺便说一句,如果你觉得 10_000 已经很多,不妨试试 100_000,甚至 1_000_000。在现代机器上,只要你的线程做的是简单工作或主要处于输入/输出等待,JVM 也能应对。

4. Runnable 与 Lambda:如何把代码交给虚拟线程

虚拟线程与普通线程一样通过接口 Runnable 接收任务。这意味着你可以传入 Lambda 表达式、方法引用,或任何实现了 Runnable 的对象。

Lambda 示例:

Thread.ofVirtual().start(() -> System.out.println("虚拟线程中的 Lambda!"));

方法引用示例:

public class TaskRunner {
    public static void main(String[] args) {
        Thread.ofVirtual().start(TaskRunner::doWork);
    }

    static void doWork() {
        System.out.println("正在虚拟线程中工作: " + Thread.currentThread());
    }
}

匿名类示例:

Thread.ofVirtual().start(new Runnable() {
    @Override
    public void run() {
        System.out.println("虚拟线程中的匿名类!");
    }
});

结论:
凡是适用于普通线程的,用在虚拟线程上一样行——只是现在更“轻量又迅速”。

5. 与 ExecutorService 的对比:旧思路与新方式

传统的 ExecutorService

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    int taskNum = i;
    executor.submit(() -> {
        System.out.println("任务 #" + taskNum + " 正在执行");
    });
}
executor.shutdown();

问题:
如果任务太多而线程太少——任务会在队列中等待。如果线程太多——程序会因资源不足而“呛住”。

新方式:基于虚拟线程的 Executor

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 100_000; i++) {
    int taskNum = i;
    executor.submit(() -> {
        System.out.println("虚拟任务 #" + taskNum);
    });
}
executor.shutdown();

发生了什么?

  • 每个任务都会创建一个独立的虚拟线程。
  • JVM 自行调度它们,而不会让系统过载。
  • 无需限制池的大小——虚拟线程“几乎零成本”。

何时使用基于虚拟线程的 Executor?

  • 当你有大量任务流入,并且不想费心考虑池的大小。
  • 当任务彼此独立,可以并行执行。
  • 当你希望简单一些:无需手动管理线程。

6. 实用建议:该用哪种方式

何时直接使用 Thread.ofVirtual().start()

  • 当需要为某个独立的任务创建单独线程(例如用于测试、演示或简单实验)。
  • 当线程数量不多,并且你希望手动管理它们。

何时使用 Executors.newVirtualThreadPerTaskExecutor()

  • 当需要批量启动任务(例如处理大量请求、文件或网络连接)。
  • 当任务彼此独立,不需要相互协调。
  • 当你想把虚拟线程集成进已有使用 ExecutorService 的架构(例如在 Web 服务器、任务处理器等场景)。

建议:
如果拿不准——就从基于虚拟线程的 Executor 开始。这是最通用、最现代的做法。

7. 在虚拟线程中处理异常

try-catch 的角度看,虚拟线程就是普通线程。如果你的 Runnable 内抛出异常,它不会“搞崩”整个 JVM,而只会让该线程以错误结束。

示例:

Thread t = Thread.ofVirtual().start(() -> {
    throw new RuntimeException("出了点问题!");
});
try {
    t.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("主线程继续工作。");

在 ExecutorService 中:
如果通过 submit 提交任务,可以通过 Future 获取结果;调用 get() 时会抛出异常:

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<?> f = executor.submit(() -> {
    throw new RuntimeException("虚拟任务中的错误");
});
try {
    f.get();
} catch (ExecutionException e) {
    System.out.println("捕获到来自虚拟线程的错误: " + e.getCause());
}
executor.shutdown();

8. 创建虚拟线程时的常见错误

错误 1:混淆虚拟线程和平台线程。 如果你通过 new Thread(...)Thread.ofPlatform() 创建线程,那就不是虚拟线程。只有 Thread.ofVirtual().start(...)Executors 中的方法才会给你真正的虚拟线程(Virtual Threads)。

错误 2:期望对重计算任务提速。 虚拟线程不会加速严重占用 CPU 的任务(CPU-bound)。如果你有一百万个线程,每个都把圆周率算到第百万位——JVM 并不能“加速”计算,它只会在这些线程之间切换。

错误 3:为每个线程都持有资源(例如数据库)。 如果你创建了一百万个虚拟线程,但每个都需要一个独立的数据库连接——数据库会吃不消。虚拟线程适合那种大部分时间都在等待(I/O),而不是大量占用受限外部资源的任务。

错误 4:在需要时却不等待线程结束。 如果主线程比虚拟线程先结束——程序可能在结果产生前就退出。请使用 join() 或者使用带有 shutdown()awaitTermination()ExecutorService

错误 5:使用与虚拟线程不兼容的过时库。 某些第三方库可能在操作系统层面阻塞线程,或使用 native 同步,从而降低虚拟线程的效率。务必检查兼容性。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION