虚拟线程探索与实践(JDK19)

优质博文:IT-BLOG-CN

一、背景

1、大量应用时同步方式,修改成异步方式投入资源大;
2、由线程池被打满引起的事故很难杜绝,很多应用将核心和非核心的应用一起交由线程池管理;

解决上面问题有两种措施:
1、NIO:优点是有成熟框架ReactorRxJava等。缺点是可读性欠缺,改造难度大;
2、虚拟线程:优点是业务侧改造成本低,无需池化,天然隔离。缺点是对nativesynchronize方法或者外部函数不友好;

二、原理

调度方式: 当前线程将任务提交给虚拟线程的时候,是一个Runnalbe状态,存放在队列中排队。任务排到第一位后,会挂在到平台线程上Platform Thread,该线程就是用户线程New Thread的线程。当任务挂载上去之后,就是一个运载线程,执行虚拟线程中的任务。当线程执行到阻塞或者IO操作的时候,它会将当前任务卸载到队列中,重新编程Runnable状态。

虚拟线程探索与实践(JDK19)_第1张图片

状态机: 与平台的线程的状态相似,我们主要看下如下两个状态的变化

RUNNING -> PARKING 与普通线程一致,通常由各种block导致 触发后置为PARKING状态,卸载虚拟线程,调用Continuation.yield()方法
RUNNING -> YIELDING 通常为IO阻塞时 置为YIELDING状态,卸载虚拟线程,调用Thread.yield

虚拟线程探索与实践(JDK19)_第2张图片

三、使用场景

计算密集型

CPU机密型: 并行开启X个任务,每个任务对5W个随机数进行排序;

虚拟线程探索与实践(JDK19)_第3张图片

结论:虚拟线程对于CPU密集型应用无优势

IO密集型

并发数 CPU 响应时间(ms) 吞吐量
10 35 19 490
20 55 20 925
30 80 22 1296
40 95 26 1468
50 99 32 1508

结论:虚拟线程在CPU使用率达到80以后,性能有些许衰退。

虚拟线程探索与实践(JDK19)_第4张图片

结论:相同的并发下
1、由于虚拟线程不需要大量的系统线程调度,节省了CPU的开销;
2、系统线程的大量减少,减少了CPU_Load排队的情况;
3、虚拟线程替换了dal的线程池,减少了线程数量(上面包含了JVM自身的线程和框架的线程);

使用案例代码:

CDubboClient instance = CDubboClient.getInstance();
FlightPassengerWS service = instance.getService(FlightPassengerWS.class);
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    StudentResponseType studentResponse = executor.submit( () -> service.getStudent(request).get());
}

四、死锁

synchronize同步代码块导致的死锁现象:

虚拟线程探索与实践(JDK19)_第5张图片

结论: 虚拟线程获取了连接后IO发生了卸载,当链接数耗尽,装载状态的虚拟线程由于拿不到链接被BLOCK,发生yield。由于在同步代码块中,yield失败发生绑定。导致其他获取链接的虚拟线程无运载线程可用。

解决办法: 使用ReentrantLock替换Synchronized

private final ReentranLock synLock = new ReentranLock();
synchronized(this) {

}
// 替换为
synLock.lock();
try {

} finally {
    synLock.unlock();
}

五、实践

QPS 1000+ 的项目性能监控

平台线程 虚拟线程
CPU使用率(avg) 20% 15%
CPU_Load(max) 15 5.5
线程数(avg) 260 258
时间响应(avg) 118ms 97ms
P99.9 3460ms 1676ms

使用虚拟线程后,由于切换了线程,无法从HttpContext.current()获取到任何信息,需要在虚拟线程里threadlocal重新set

你可能感兴趣的:(Java并发编程(多线程),java,redis,数据库)