Java虚拟线程(二)

前面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("自定义虚拟线程池,执行任务"));

posted on 2026-06-11 15:42  幽州散人  阅读(2)  评论(0)    收藏  举报

导航