Виртуальные потоки 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В этом примере, когда виртуальный поток входит в синхронизированный блок, он закрепляется за своим потоком-носителем, но это не всегда так. Одного ключевого слова Synchronized в Java недостаточно, чтобы вызвать закрепление потоков в виртуальных потоках. Для закрепления потока в синхронизированном блоке должна быть точка блокировки, которая заставляет виртуальный поток инициировать парковку и в конечном итоге запрещает отключение от потока-носителя. Закрепление потоков может привести к снижению производительности, поскольку сведет на нет преимущества использования облегченных/виртуальных потоков.
Каждый раз, когда виртуальный поток сталкивается с точкой блокировки, его состояние меняется на 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
Флаг -Djdk.tracePinnedThreads=full — это аргумент запуска JVM, который предоставляет подробную информацию о трассировке закрепления виртуального потока. Если этот параметр включен, он регистрирует такие события, как:
Используйте этот флаг разумно только во время сеансов отладки, так как это приводит к снижению производительности.
Скомпилируйте наш демо-код:
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.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 являются свидетельством эволюции и возможностей языка. Они предлагают новую, облегченную альтернативу традиционным потокам ОС, обеспечивая мост к эффективному управлению параллелизмом. Потратив время на глубокое изучение и понимание ключевых концепций, таких как закрепление потоков, вы можете дать разработчикам ноу-хау, позволяющие использовать весь потенциал этих легких потоков. Эти знания не только подготавливают разработчиков к использованию будущих функций, но и позволяют им более эффективно решать сложные проблемы управления параллелизмом в их текущих проектах.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3