jdk21正式释放了virtual thread特性,可以在io阻塞的高并发开发中,让传统的编码方式达到一个质的飞跃。
⚠️但是,这里需要注意,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(); } }); }