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;
}
}
}
我们使用VirtualExecutorService的submit(runnable)方法向vtExecutor提交Runnable任务,但在提交之前需要先semaphore.acquire()获取信号量,完成任务或异常的时候semaphore.release()释放信号量。
注意:我们这里semaphore.acquire()放在向virtualThreadExecutor提交的runnable.run方法之外,是为了向外层调用者(比如tomcat虚拟线程)传递背压,否则虽然理论上virtualThreadExecutor的最大并发仍然是50,但是调用者没被挡住,仍然可以无限制提交。
使用如下所示:
virtualExecutorService.submit(() -> LOG.info("自定义虚拟线程池,执行任务"));
浙公网安备 33010602011771号