」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 探索 JVM 虛擬執行緒機制中的固定

探索 JVM 虛擬執行緒機制中的固定

發佈於2024-11-08
瀏覽:305

Java 的虚拟线程提供了传统操作系统线程的轻量级替代方案,从而实现了高效的并发管理。但了解他们的行为对于获得最佳表现至关重要。这篇博文深入探讨了固定(一种可能影响虚拟线程执行的场景),并探讨了监控和解决该问题的技术。

虚拟线程:一种轻量级并发方法

Java 的虚拟线程是运行在底层操作系统线程(载体线程)之上的托管实体。与创建大量操作系统线程相比,它们提供了一种更有效的处理并发的方法,因为它们产生的开销较低。 JVM动态地将虚拟线程映射到承载线程,从而更好地利用资源。

  • 由 JVM 管理:与操作系统直接管理的 OS 线程不同,虚拟线程由 Java 虚拟机 (JVM) 创建和调度。这允许在 JVM 环境中进行更细粒度的控制和优化。

  • 减少开销:与操作系统线程相比,创建和管理虚拟线程所产生的开销显着降低。这是因为 JVM 可以利用较少数量的底层操作系统线程来有效管理更大的虚拟线程池。

  • 与现有代码的兼容性:虚拟线程被设计为与现有Java代码无缝集成。它们可以与传统操作系统线程一起使用,并在 Executor 和 ExecutorService 等熟悉的结构中工作,用于管理并发。

下图展示了虚拟线程和平台线程的关系:

Exploring Pinning in JVM


固定:当虚拟线程卡住时

当虚拟线程与其承载线程绑定时,就会发生固定。这本质上意味着虚拟线程在处于固定状态时不能被抢占(切换到另一个承载线程)。以下是触发固定的常见场景:

  • 同步块和方法:在同步块或方法中执行代码会导致固定。这确保了对共享资源的独占访问,防止数据损坏问题。

代码示例:

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 



在此示例中,当虚拟线程进入同步块时,它会固定到其承载线程,但这并不总是正确的。仅Java的synchronized关键字不足以导致虚拟线程中的线程固定。为了发生线程固定,同步块内必须有一个阻塞点,该阻塞点会导致虚拟线程触发暂停,并最终不允许从其承载线程卸载。线程固定可能会导致性能下降,因为它会抵消使用轻量级/虚拟线程的好处。

每当虚拟线程遇到阻塞点时,其状态就会转换为 PARKING。这种状态转换是通过调用 VirtualThread.park() 方法来指示的:

// 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 监视固定

-Djdk.tracePinnedThreads=full 标志是一个 JVM 启动参数,它提供有关虚拟线程固定的详细跟踪信息。启用后,它会记录以下事件:

  • 固定涉及的虚拟线程ID
  • 虚拟线程固定的承载线程ID
  • 堆栈跟踪指示导致固定的代码部分

仅在调试会话期间明智地使用此标志,因为它会带来性能开销。

  1. 编译我们的演示代码:

    javac Main.java
    
  2. 使用 -Djdk.tracePinnedThreads=full 标志启动编译的代码:

    java -Djdk.tracePinnedThreads=full Main
    
  3. 观察控制台中的输出,其中显示了有关虚拟线程固定的详细信息:

    Thread[#29,ForkJoinPool-1-worker-1,5,CarrierThreads]
    java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183)
    java.base/jdk.internal.vm.Continuation.onPinned0(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) 
    
    

使用可重入锁修复固定

固定是一种不良情况,它会影响虚拟线程的性能。可重入锁是抵消固定的有效工具。以下是如何使用可重入锁来缓解锁定情况:

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 



在更新的示例中,我们使用 ReentrantLock 而不是同步块。线程可以获取锁并在完成操作后立即释放它,与可能持有锁较长时间的同步块相比,可能会减少锁定的持续时间。

综上所述

Java 的虚拟线程证明了该语言的发展和功能。它们为传统操作系统线程提供了一种全新的、轻量级的替代方案,为高效的并发管理提供了一座桥梁。花时间深入挖掘并理解线程固定等关键概念可以让开发人员掌握充分利用这些轻量级线程潜力的专业知识。这些知识不仅帮助开发人员为利用即将推出的功能做好准备,还使他们能够在当前项目中更有效地解决复杂的并发控制问题。

版本聲明 本文轉載於:https://dev.to/yanev/exploring-pinning-in-jvms-virtual-thread-mechanism-5h13?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 在 Go 中使用 WebSocket 進行即時通信
    在 Go 中使用 WebSocket 進行即時通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSocke...
    程式設計 發佈於2024-12-26
  • Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta:列偏移的刪除和恢復Bootstrap 4 在其Beta 1 版本中引入了重大更改柱子偏移了。然而,隨著 Beta 2 的後續發布,這些變化已經逆轉。 從 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    程式設計 發佈於2024-12-26
  • 如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    在 PHP 中組合關聯數組在 PHP 中,將兩個關聯數組組合成一個數組是常見任務。考慮以下請求:問題描述:提供的代碼定義了兩個關聯數組,$array1 和 $array2。目標是建立一個新陣列 $array3,它合併兩個陣列中的所有鍵值對。 此外,提供的陣列具有唯一的 ID,而名稱可能重疊。要求是建...
    程式設計 發佈於2024-12-26
  • 儘管程式碼有效,為什麼 POST 請求無法擷取 PHP 中的輸入?
    儘管程式碼有效,為什麼 POST 請求無法擷取 PHP 中的輸入?
    解決PHP 中的POST 請求故障在提供的程式碼片段中:action=''而非:action="<?php echo $_SERVER['PHP_SELF'];?>";?>"檢查$_POST陣列:表單提交後使用 var_dump 檢查 $_POST 陣列的內...
    程式設計 發佈於2024-12-26
  • 插入資料時如何修復「常規錯誤:2006 MySQL 伺服器已消失」?
    插入資料時如何修復「常規錯誤:2006 MySQL 伺服器已消失」?
    插入記錄時如何解決「一般錯誤:2006 MySQL 伺服器已消失」介紹:將資料插入MySQL 資料庫有時會導致錯誤「一般錯誤:2006 MySQL 伺服器已消失」。當與伺服器的連線遺失時會出現此錯誤,通常是由於 MySQL 配置中的兩個變數之一所致。 解決方案:解決此錯誤的關鍵是調整wait_tim...
    程式設計 發佈於2024-12-26
  • HTML 格式標籤
    HTML 格式標籤
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    程式設計 發佈於2024-12-26
  • 大批
    大批
    方法是可以在物件上呼叫的 fns 數組是對象,因此它們在 JS 中也有方法。 slice(begin):將陣列的一部分提取到新數組中,而不改變原始數組。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index ...
    程式設計 發佈於2024-12-26
  • 如何在 HTML 表格中有效地使用 Calc() 和基於百分比的欄位?
    如何在 HTML 表格中有效地使用 Calc() 和基於百分比的欄位?
    在表格中使用Calc():克服百分比困境創建具有固定寬度列和可變寬度列的表格可能具有挑戰性,尤其是在嘗試在其中使用calc() 函數。 在 HTML 中,使用 px 或 em 設定固定列寬非常簡單。但是,對於可變寬度列,通常使用百分比 (%) 單位。然而,當在表中使用 calc() 時,百分比似乎無...
    程式設計 發佈於2024-12-26
  • 如何在PHP中透過POST提交和處理多維數組?
    如何在PHP中透過POST提交和處理多維數組?
    在PHP 中透過POST 提交多維數組當使用具有可變長度的多列和行的PHP 表單時,有必要進行轉換輸入到多維數組中。這是解決這項挑戰的方法。 首先,為每列分配唯一的名稱,例如:<input name="topdiameter[' current ']" type="...
    程式設計 發佈於2024-12-26
  • for(;;) 迴圈到底是什麼、它是如何運作的?
    for(;;) 迴圈到底是什麼、它是如何運作的?
    揭秘神秘的for(;;) 循環在古老的程式碼庫深處,你偶然發現了一個令人困惑的奇特for 循環你的理解。其顯示如下:for (;;) { //Some stuff }您深入研究線上資源,但發現自己陷入沉默。讓我們來剖析這個神秘的構造。 for 迴圈的結構Java 中的for 迴圈遵循特定的語...
    程式設計 發佈於2024-12-25
  • Java 的 Scanner.useDelimiter() 如何使用正規表示式?
    Java 的 Scanner.useDelimiter() 如何使用正規表示式?
    Java 使用Scanner.useDelimiter 了解分隔符號Java 中使用Scanner.useDelimiter 了解分隔符號Java 中的Scanner 類別提供了useDelimiter 方法,讓您指定分隔符號(代字或模式)來分隔代字幣。然而,使用分隔符號可能會讓初學者感到困惑。讓我...
    程式設計 發佈於2024-12-25
  • 如何在 Android 中顯示動畫 GIF?
    如何在 Android 中顯示動畫 GIF?
    在Android 中顯示動畫GIF儘管最初誤解Android 不支援動畫GIF,但實際上它具有解碼和顯示動畫的能力顯示它們。這是透過利用 android.graphics.Movie 類別來實現的,儘管這方面沒有廣泛記錄。 要分解動畫 GIF 並將每個幀作為可繪製對象合併到 AnimationDra...
    程式設計 發佈於2024-12-25
  • 為什麼我在執行 phpize 時出現「找不到 config.m4」錯誤?
    為什麼我在執行 phpize 時出現「找不到 config.m4」錯誤?
    解決phpize 中的“找不到config.m4”錯誤在運行phpize 時遇到“找不到config.m4”錯誤是可能阻礙ffmpeg 等擴充安裝的常見問題。以下是解決此錯誤並讓 phpize 啟動並運行的方法。 先決條件:您已經安裝了適合您的PHP 版本的必要開發包,例如php- Debian/U...
    程式設計 發佈於2024-12-25
  • 列印時如何在每頁重複表頭?
    列印時如何在每頁重複表頭?
    在印刷模式下重複表格標題當表格在印刷過程中跨越多個頁面時,通常需要有標題行(TH元素)在每頁重複,以便於參考。 CSS 提供了一種機制來實現此目的。 解決方案:使用 THEAD 元素CSS 中的 THEAD 元素是專門為此目的而設計的。它允許您定義一組應在每個列印頁面上重複的標題行。使用方法如下:將...
    程式設計 發佈於2024-12-25
  • 為什麼 `cout` 會誤解 `uint8_t` 以及如何修復它?
    為什麼 `cout` 會誤解 `uint8_t` 以及如何修復它?
    深入分析:為什麼 uint8_t 無法正確列印您遇到了 uint8_t 變數的值無法正確列印的問題庫特。經過調查,您發現將資料類型變更為 uint16_t 可以解決該問題。此行為源自於 uint8_t 的基本性質以及 cout 處理字元資料的方式。 uint8_t 在內部儲存一個無符號 8 位元整數...
    程式設計 發佈於2024-12-25

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3