探索 JVM 虚拟线程机制中的固定
2024-09-04 20:11:19
java 虚拟线程提供了传统操作系统线程的轻量级替代方案,可以实现高效的并发管理。但了解他们的行为对获得最佳性能至关重要。本博客深入讨论了固定(可能影响虚拟线程执行的场景),并讨论了监控和解决问题的技术。
虚拟线程:轻量级并发法java 虚拟线程是操作在底层操作系统线程(载体线程)上的托管实体。由于成本较低,它们提供了一种更有效的并发处理方法,而不是创建大量的操作系统线程。 jvm 虚拟线程动态映射到载体线程,从而实现更好的资源利用率。
由 jvm 管理:与操作系统直接管理的操作系统线程不同,虚拟线程是由 java 虚拟机 (jvm) 创建和调度。这是允许的 jvm 控制和优化环境中的更细粒度。
降低成本:创建和管理虚拟线程的成本明显低于操作系统线程。这是因为 jvm 底层操作系统的线程数量较少,可以有效地管理更大的虚拟线程池。
与现有代码兼容:虚拟线程旨在与现有代码兼容 java 代码无缝集成。它们可以与传统的操作系统线程一起使用 executor 和 executorservice 在熟悉的结构中工作,用于并发管理。
下图显示了虚拟线程与平台线程之间的关系:
当虚拟线程卡住时,固定:当虚拟线程与其载体线程绑定时,就会发生固定。这本质上意味着当虚拟线程处于固定状态时(切换到另一个载体线程)不能被占用。以下是触发固定的常见场景:
- 同步块和方法:在同步块或方法中执行代码会导致固定。这可以保证共享资源的独家访问,防止数据损坏。
代码示例:
import java.util.concurrent.executorservice; import java.util.concurrent.executors; public class main { public static void main(string[] args) throws interruptedexception { final counter counter = new counter(); runnable task = () -> { for (int i = 0; i <p>这个例子中,当虚拟线程进入同步块时,它将固定到其承载线程,但这并不总是正确的。只有java的synchronized关键字不足以导致虚拟线程中的线程固定。java的synchronized关键词不足以导致虚拟线程中的线程固定。为了固定线程,同步块中必须有一个阻塞点,这将导致虚拟线程触发和暂停,最终不允许从其承载线程卸载。由于使用轻量级/虚拟线程的好处,线程固定可能会导致性能下降。</p> <p>当虚拟线程遇到阻塞点时,其状态将转换为 parking。这种状态转换是通过调用来转换的 virtualthread.park() 方法指示:<br></p> <pre class="brush:php;toolbar:false">// jdk core code void park() { assert thread.currentthread() == this; // complete immediately if parking permit available or interrupted if (getandsetparkpermit(false) || interrupted) return; // park the thread setstate(parking); try { if (!yieldcontinuation()) { // park on the carrier thread when pinned parkoncarrierthread(false, 0); } } finally { assert (thread.currentthread() == this) && (state() == running); } }
让我们看一个代码示例来解释这个概念:
import java.util.concurrent.executorservice; import java.util.concurrent.executors; public class main { public static void main(string[] args) { counter counter = new counter(); runnable task = () -> { for (int i = 0; i
- 本机方法/外部函数:运行本机方法或外部函数也可能导致固定。在这些操作过程中,jvm 虚拟线程的状态可能无法有效管理。
-djdk.tracepinnedthreads=full 标志是一个 jvm 启动参数,提供关于虚拟线程固定的详细跟踪信息。启用后,它将记录以下事件:
- 虚拟线程id与固定相关
- 虚拟线程固定的承载线程id
- 导致固定代码部分的堆栈跟踪指示
这个标志只在调试会话中明智地使用,因为它会带来性能费用。
-
编译我们的演示代码:
javac main.java
-
使用 -djdk.tracepinnedthreads=full 开始编译标志的代码:
java -djdk.tracepinnedthreads=full main
-
观察控制台中的输出,显示虚拟线程固定的详细信息:
thread[#29,forkjoinpool-1-worker-1,5,carrierthreads] java.base/java.lang.virtualthread$vthreadcontinuation.onpinned(virtualthread.java:183) java.base/jdk.internal.vm.continuation.onpined0(continuation.java:393) java.base/java.lang.virtualthread.parknanos(virtualthread.java:621) java.base/java.lang.virtualthread.sleepnanos(virtualthread.java:791) java.base/java.lang.thread.sleep(thread.java:507) counter.increment(main.java:38) <== monitors:1 main.lambda$main\$0(main.java:13) java.base/java.lang.virtualthread.run(virtualthread.java:309) final counter value: 200
固定是一种影响虚拟线程性能的不良情况。可重入锁是抵消固定的有效工具。如何使用可重入锁来缓解固定:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; public class Main { public static void main(String[] args) { Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; i < 100; i++) { counter.increment(); } }; Thread thread1 = Thread.ofVirtual().start(task); Thread thread2 = Thread.ofVirtual().start(task); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("Final counter value: " + counter.getCount()); } } class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { Thread.sleep(100); // This simulates a blocking call count++; } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public int getCount() { return count; } }
我们使用更新示例 reentrantlock 而不是同步块。线程可以获得锁并在完成操作后立即释放。与可能持有锁更长时间的同步块相比,锁的持续时间可能会缩短。
综上所述java 这种语言的发展和功能见证了虚拟线程。它们为传统操作系统线程提供了全新的轻量级替代方案,为高效并发管理提供了桥梁。深入挖掘和理解线程固定等关键概念可以让开发人员掌握如何充分利用这些轻量级线程的潜力。这些知识不仅帮助开发人员准备使用即将到来的功能,而且使他们能够更有效地解决当前项目中复杂的并发控制问题。
以上就是探索 JVM 更多关于图灵教育的其他相关文章,请关注虚拟线程机制中固定的详细内容!