惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
人人都是产品经理
人人都是产品经理
月光博客
月光博客
V
V2EX
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
V
Visual Studio Blog
宝玉的分享
宝玉的分享
雷峰网
雷峰网
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
博客园 - 聂微东
博客园 - 司徒正美
博客园 - 【当耐特】
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志

博客园 - 幽州散人

SQL优化案例 synchronized 为啥会让虚拟线程卸载失效 主线任务和使命 温州3日之旅 CompletableFuture + 异步Servlet:同步的姿势、异步的效果 Snowflake雪花算法与发号器的应用 windows Java 幽灵转发壳进程 也谈MySQL limit offset深翻页问题 SHOW SESSION STATUS 与 Handler 变量 MySQL 5.7 Profiles 执行性能分析 MySQL临时表与文件排序 MySQL 5.7 MRR多范围读优化 MySQL 5.7 ICP索引条件下推优化 MySQL符合索引与最左前缀原则 从I/O 的物理成本理解回表和索引覆盖 第4个电瓶以及补漆 Flink原理:并行度与keyGroup桶 什么是数字批发银行 Java虚拟线程(三)实现原理 Java虚拟线程(一) 大模型推理层服务化架构 传奇调查员之路:理智值-99,但我必须听懂深渊的语言 js里调用智能合约读/写函数的方法的区别 字节与其16进制字符表示转换的bug Redisson分布式锁 交易心得 DexScreener接口初探 某安全软件跑飞了。。 Ed25519算法签名与验签的Java实现 Tendermint拜占庭容错引擎
Java虚拟线程(二)
幽州散人 · 2026-06-11 · via 博客园 - 幽州散人

前面Java虚拟线程(一)提到过,虚拟线程的世界没有线程池了,虚拟线程想用就开,用完等GC,但是线程池还有隔离和限制并发的作用,虚拟线程的场景可以用信号量解决。

“虚拟线程的世界没有线程池” 是一种简化说法,实际上虚拟线程底层的由JVM管理的承载线程是一个ForkJoinPool线程池

关于隔离和限制并发,虚拟线程创建的代价很小,但也不能不加限制,因为要考虑其任务涉及的下游资源的情况,举个例子,我们创建虚拟线程去操作MySQL,但是数据库连接池的连接数是有限的,我们需要限制虚拟线程最大并发,防止把连接池打满。

@Configuration
public class VTExecutorConfig {

    @Bean
    public ExecutorService vtExecutor(){
        ThreadFactory factory = Thread.ofVirtual().name("vt-worker-", 1).factory();
        return Executors.newThreadPerTaskExecutor(factory);
    }
}

比如上面这个ExecutorService ,submit给它的runable照单全收,按现在的常见内存容量,启动几万个都没问题,但是下游就不一定吃的消了。

本篇我们来封装一个使用信号量的、具备限制最大并发功能的虚拟“线程池”。

我们的思路是在上面的那种ExecutorService基础上再包一层,使得Runnable丢给它执行之前,先去拿一个信号量,执行完任务之后再释放信号量Semaphore,而信号量总数就是限制的最大并发。

import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Service
public class VirtualExecutorService {
    private final int LIMIT = 50;
    private final ExecutorService virtualThreadExecutor = Executors.newThreadPerTaskExecutor(
            Thread.ofVirtual().name("vThread-pool-", 1).factory()
    );
    private final Semaphore semaphore = new Semaphore(LIMIT);

    public void submit(Runnable runnable){
        try {
            semaphore.acquire(); //等待过程中,可能会被中断
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); //重新置回虚拟线程的中断标志(标准做法,让上层也能感知)
            throw new RuntimeException(e);
        }
        try {
            virtualThreadExecutor.submit(() -> {
                try{
                    runnable.run();
                } finally {
                    semaphore.release();
                }
            });
        } catch (RuntimeException e){
            semaphore.release();
            throw e;
        }
    }
}

我们使用VirtualExecutorServicesubmit(runnable)方法向vtExecutor提交Runnable任务,但在提交之前需要先semaphore.acquire()获取信号量,完成任务或异常的时候semaphore.release()释放信号量。

注意:我们这里semaphore.acquire()放在向virtualThreadExecutor提交的runnable.run方法之外,是为了向外层调用者(比如tomcat虚拟线程)传递背压,否则虽然理论上virtualThreadExecutor的最大并发仍然是50,但是调用者没被挡住,仍然可以无限制提交。

使用如下所示:

virtualExecutorService.submit(() -> LOG.info("自定义虚拟线程池,执行任务"));