Pull to refresh
4
6.1
Иван Попов @JavaChampion

User

Send message

Если код исполняется в виртуальном потоке, Java под капотом вызовет вместо блокирующего API операционной системы, более оптимальный неблокирующий аналог, такой как epoll. Виртуальные потоки - это по сути задачи, которые исполняет отдельный ForkJoinPool. Когда исполнение дошло до блокирующей операции, Java заменяет её на неблокирующий аналог, на результат подписывается отдельный специальный поток-unparker и как только от ОС поступает сигнал, что данные получены, этот unparker отправляет задачу обратно в ForkJoinPool, чтобы она продолжила исполняться.

Важно, что в ForkJoinPool не должно происходить никаких блокировок, иначе получаем pinning-проблему, вероятность который всё меньше и меньше с каждой новой версией джавы )

Чем больше блокирующих операций, или чем они дольше, тем больше бенефит от такого подхода. Но кажется, тут каждый отдельный кейс уникален и без нагрузочного тестирования не обойтись. Благо, это не сложно написать код так, чтобы виртуальные потоки можно было легко выключить, если нагрузочное тестирование покажет, что выигрыша от них нет. Благо контракты не меняются: VirtualThread extends Thread и есть реализация ExecutorService, которая использует виртуальные потоки. Также есть флаг в spring boot и многих других фреймворках, чтобы легко включить или выключить виртуальные потоки:

spring.threads.virtual.enabled: true

Да, имелся в виду Project Reactor.

Согласен, что внедрять надо аккуратно. В случае http-аггрегатора польза будет максимальная, но возможны другие кейсы. Например, если сервис получает обновления в реальном времени с биржи по веб-сокетам и грузит в какую-то внутреннюю систему: здесь пропускная способность очень важна и можно рассмотреть использование виртуальных потоков.

Что касается synchronized - действительно часто встречается внутри ThreadSafe структур данных. Но важно помнить, что с проблемой столкнёмся только в том случае, если внутри synchonized блока есть блокирующая операция, или вызов wait метода. В Java 21 есть стандартный способ проверить, что нет проблемы с pinning: есть флаг -Djdk.tracePinnedThreads=full. И, чтобы проверить, что мониторинг пиннинга реально работает, можно специально, при старте сервиса запустить код, который вызовет пиннинг на секунду, при старте сервиса:

BlockingOperation.java:

import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;

import java.time.Duration;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class BlockingOperation implements Runnable {
private final Duration duration;

@Override
public void run() {
try {
sleep(duration.toMillis());
} catch (InterruptedException e) {
currentThread().interrupt();
}
}

}

SyncronizedBlockingOperation.java:

import static java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor;

import java.time.Duration;

public class SynchronizedBlockingOperation extends BlockingOperation {

public SynchronizedBlockingOperation(Duration duration) {
super(duration);
}

public static void createAndRun(Duration duration) {
try (final var executor = newVirtualThreadPerTaskExecutor()) {
executor.execute(new SynchronizedBlockingOperation(duration));
}
}

@Override
public synchronized void run() {
super.run();
}

}

В main методе:

package ru.rshb.fixadapter;

import java.time.Duration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FixAdapterApplication {

public static void main(String[] args) {
SpringApplication.run(FixAdapterApplication.class, args);
SynchronizedBlockingOperation.createAndRun(Duration.ofSeconds(1));
}

}


В Java 24 проблему действительно решили. Там пиннинг практически невозможно вызвать. Но теоретически всё ещё возможно, при вызове блокирующей операции из нативного кода.

Мы у себя в компании часть сервисов обновим до Java 25 LTS сразу, спустя несколько месяцев после того как она выйдет осенью этого года.

Information

Rating
1,118-th
Registered
Activity

Specialization

Backend Developer, Web Developer
Git
SQL
PostgreSQL
OOP
Java
Spring Boot
Hibernate
REST
Junit
Java Core