jdk21正式释放了virtual thread特性,可以在io阻塞的高并发开发中,也能在性能上达到一个质的飞跃。经过网友的测试,一般能提升到300%的吞吐。
但是,有些情况下,性能不但没有提升,反而下降的特别厉害。其中一个原因,就是下面描述的synchronized关键字导致。
⚠️这里需要注意,synchronized关键字,在jdk21、jdk22、jdk23中,会让当前虚拟线程,绑定(Pinning)在os平台线程上,包裹的代码执行完成后,才会从os平台线程卸载。
而一个os的平台线程,一般是当前机器的CPU核心数。例如4核带了超线程=4*2=8个。
换句话说:如果在synchronized关键字的代码中,有一段block阻塞的操作。同时有8个线程都执行了到这段代码中,会导致其他的虚拟线程没有os平台线程可用,一直阻塞等待有空闲的os平台线程。这样会导致本该高速并行的代码,变为排队执行。
这个现象已经被JDK研发团队感知,收集到jep中,可能在jdk24修复,预计在jdk25 LTS版本中完全释放。
https://openjdk.org/jeps/491
https://openjdk.org/projects/jdk/24/
当前解决方案:使用ReentrantLock来替代synchronized。
从下面的代码可以进行测试
public class TestVirtual {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
private ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock().writeLock();
private static final Object lock = new Object();
// 因为是有synchronized,pinning了os线程,每次执行8个线程,导致后续虚拟线程一直等待
public void testSync() {
int i = atomicInteger.incrementAndGet();
log.info("testSync atomicInteger start = {}", i);
synchronized (this) {
log.info("testSync atomicInteger = {}", i);
try {
Thread.sleep(10 * 1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("testSync atomicInteger after = {}", i);
}
}
// 无影响,不会pinning os线程,所有线程都能正常执行
public void testReentrantLock() {
int i = atomicInteger.incrementAndGet();
log.info("testReentrantLock atomicInteger start = {}", i);
writeLock.lock();
try {
log.info("testReentrantLock atomicInteger = {}", i);
Thread.sleep(10 * 1000L);
log.info("testReentrantLock atomicInteger after = {}", i);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
writeLock.unlock();
}
}
// 因为sync的是static变量,导致每个os线程上都pinning了一个虚拟线程,并且每次只能执行1个,导致性能损失更大
public void testSyncGlobal() {
int i = atomicInteger.incrementAndGet();
log.info("testSyncGlobal atomicInteger start = {}", i);
synchronized (lock) {
log.info("testSyncGlobal atomicInteger = {}", i);
try {
Thread.sleep(10 * 1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("testSyncGlobal atomicInteger after = {}", i);
}
}
}
// 因为是有synchronized,pinning了os线程,每次执行8个线程,导致后续虚拟线程一直等待
for (int i = 0; i < 100; i++) {
TestVirtual virtual = new TestVirtual();
Thread.startVirtualThread(new Runnable() {
@Override
public void run() {
virtual.testSync();
}
});
}
// 无影响,不会pinning os线程,所有线程都能正常执行
for (int i = 0; i < 100; i++) {
TestVirtual virtual = new TestVirtual();
Thread.startVirtualThread(new Runnable() {
@Override
public void run() {
virtual.testReentrantLock();
}
});
}
// 因为sync的是static变量,导致每个os线程上都pinning了一个虚拟线程,并且每次只能执行1个,导致性能损失更大
for (int i = 0; i < 100; i++) {
TestVirtual virtual = new TestVirtual();
Thread.startVirtualThread(new Runnable() {
@Override
public void run() {
virtual.testSyncGlobal();
}
});
}
沪公网安备 31011502001064号