Многопоточность и виртуальные потоки

Spring framework очень большая штука и пришлось потратить полдня, чтобы изучить сторону с @Async выполнением и виртуальными потоками.

Чтобы у нас не терялась трассировка и контекст при выполнении кода в отдельных потоках необходимо задать отдельную, простую конфигурацию.

Вот целиком конфигурация

@Configuration
public class AsyncConfig {
    @Bean
    public ContextSnapshotFactory contextSnapshotFactory() {
        return ContextSnapshotFactory.builder().build();
    }

    @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
    public AsyncTaskExecutor applicationTaskExecutor(ContextSnapshotFactory contextSnapshotFactory) {
        ThreadFactory virtualThreadFactory = Thread.ofVirtual()
                .name("async-vt-", 0)
                .factory();
        ExecutorService virtualExecutor = Executors.newThreadPerTaskExecutor(virtualThreadFactory);

        Executor decoratedExecutor = ContextExecutorService.wrap(
                virtualExecutor,
                contextSnapshotFactory::captureAll
        );
        return new TaskExecutorAdapter(decoratedExecutor);
    }

    @Bean(name="virtualThreadsParallelExecutor", destroyMethod = "close")
    public ExecutorService parallelExecutor(ContextSnapshotFactory contextSnapshotFactory) {
        ThreadFactory virtualThreadFactory = Thread.ofVirtual()
                .name("parallel-custom-vt-", 0)
                .factory();

        ExecutorService virtualExecutor = Executors.newThreadPerTaskExecutor(virtualThreadFactory);

        return ContextExecutorService.wrap(
                virtualExecutor,
                contextSnapshotFactory::captureAll
        );
    }
}

ContextSnapshotFactory тут то, что будет заполнять контекст трассировки и прочего в новом потоке.

virtualThreadsParallelExecutor — кастомный экзекьютор, который можно инжектить в код. Удобно, не нужно обвесы делать.

AsyncTaskExecutor — экзекьютор, который будет выполнять код методов под аннотацией @Async