一、虚拟线程核心概念
1.1 虚拟线程与协程对比
虚拟线程
特性 | 虚拟线程 | 协程(Kotlin/Go) |
---|---|---|
阻塞行为 | 自动释放平台线程(yielding) | 非抢占式调度(协作式) |
调度机制 | JVM抢占式调度 | 运行时库协作式调度 |
系统资源消耗 | 约1KB栈内存 | 约2KB-4KB栈内存 |
阻塞优化 | 阻塞自动切换线程 | 需手动挂起/让出线程 |
代码示例:
// 虚拟线程自动释放平台线程
Thread.ofVirtual().start {
Socket().use { s ->
s.connect(InetSocketAddress("example.com", 80))
// 阻塞IO自动让出线程
val isv = s.getInputStream()
isv.read()
}
}
二、虚拟线程创建
2.1 创建
- 直接创建执行
Thread.startVirtualThread {
Thread.currentThread().setName("virtual thread")
}
- 通过构造器
// 使用 unstarted 方法创建并启动
val vt = Thread.ofVirtual().unstarted {
Thread.currentThread().name = "virtual thread"
}
vt.start()
// 直接 start 方法创建
Thread.ofVirtual().start {
Thread.currentThread().name = "virtual thread"
}
- 通过线程工厂
val factory = Thread.ofVirtual().factory()
val vt = factory.newThread {
Thread.currentThread().name = "virtual thread"
}
vt.start()
- 通过线程池
val executor = Executors.newVirtualThreadPerTaskExecutor()
executor.execute {
Thread.currentThread().name = "virtual thread"
}
2.2 简单封装
suspend fun main() {
vt {
Thread.currentThread().setName("virtual thread")
}
}
fun vt(callback: () -> Unit) {
Thread.startVirtualThread {
callback()
}
}
2.3 创建一个虚拟线程池,如果版本过低则返回null
/**
* 创建一个虚拟线程池
*/
val virtualThreadExecutor: ExecutorService? by lazy {
runCatching {
val clazz = Executors::class.java
val method = clazz.getMethod("newVirtualThreadPerTaskExecutor")
method.isAccessible = true
method.invoke(null) as ExecutorService
}.getOrNull()
}
2.4 创建一个虚拟线程,如果版本过低则使用协程
/**
* 启动一个虚拟线程,如果没有虚拟线程则启动一个IO协程
*/
fun <T> virtualThread(block: suspend CoroutineScope.() -> T): Future<T> {
val future = CompletableFuture<T>()
if (virtualThreadExecutor != null) {
return virtualThreadExecutor!!.submit {
runBlocking(block = block)
} as Future<T>
}
CoroutineScope(Dispatchers.Default).launch {
kotlin.runCatching { block().apply { future.complete(this) } }.onFailure {
future.completeExceptionally(it)
}
}
return future
}
2.5 创建一个虚拟线程调度器
kotlin 可以通过 asCoroutineDispatcher 方法将线程池转为一个协程调度器,其中则可以包括虚拟线程池
val VT = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()
// 使用协程
CoroutineScope(VT).launch {
println(51)
}
2.6 虚拟线程调度器
三、并发控制优化
3.1 锁优化
不推荐使用 synchronized ,因为 synchronized 会将资源在单一线程中锁起来,导致如果存在多个虚拟线程执行,每个虚拟线程中只有一个能访问到该资源
// 使用synchronized的典型阻塞场景
@Synchronized
fun blockingMethod() {
// 当前平台线程完全阻塞
// 其他虚拟线程无法复用该线程资源
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
}
}
推荐使用 ReentrantLock 。ReentrantLock,也被称为“可重入锁”,是一个同步工具类,在java.util.concurrent.locks包下。这种锁的一个重要特点是,它允许一个线程多次获取同一个锁而不会产生死锁
private val lock = ReentrantLock()
fun nonBlockingMethod() {
if (lock.tryLock()) {
try {
// 执行临界区代码
} finally {
lock.unlock()
}
} else {
// 处理获取失败逻辑
// 可选择延迟重试或任务转移
}
}
// 超时机制
fun safeMethod() {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
// 安全执行业务逻辑
} finally {
lock.unlock()
}
} else {
throw TimeoutException("锁获取超时")
}
}
3.2 ScopedValue
在 Java 21+ 开始引入了 ScopedValue ,尽管 ScopedValue 在 21 还是预览版本。但是相比 ThreadLocal 还是更加推荐 ScopedValue 。
支持在线程内和线程间共享不可变数据。它们优于线程局部变量,尤其是在使用大量虚拟线程时
// 上下文传递
val USER_CTX = ScopedValue.newInstance<String?>()
fun handleRequest() {
ScopedValue.where<String?>(USER_CTX, "user123").run(Runnable {})
}
四、最佳实践指南
- 避免过度线程本地存储:
// 优化前
ThreadLocal<Buffer> buf = ThreadLocal.withInitial(() -> new Buffer(1MB));
// 优化后
class Context {
Buffer buffer = Buffer(1MB); // 与虚拟线程生命周期绑定
}
- 结构化并发模式:
ShutdownOnFailure().use { scope ->
val f1: Future<Int?> = scope.fork<Any?>(this::compute1)
val f2: Future<Int?> = scope.fork<Any?>(this::compute2)
scope.join()
return f1.result() + f2.result()
}