簡單記錄下 Spring Boot 使用虛擬線程Virtual Threads(Java的協程)的方法

在之前的文章中,簡單描述了將spring boot 2.x升級spring boot 3.1的版本的過程。

本文將簡單介紹如何在spring 中引入虛擬線程,在文章最後會放上一些關於虛擬線程的官方參考資料。

JDK 22會引來重要特性,Virtual Threads也就是協程功能。

與主流的async、await方案(C#、JS等語言)相比,Java屬於stackfull coroutine有棧協程。

Java的虛擬線程API和舊版線程有良好的兼容性,升級成本非常低,還引入了結構化併發等多種工具類輔助開發人員更好的編程。

(再也不用羨慕go語言的go func(){} 了,現在Java也能只需要 Thread.startVirtualThread(() -> {  })  )

 

在Spring中使用,你至少需要Spring 6以及版本。

如果是Maven構建的項目,需要加入預覽的編譯選項。虛擬線程是JDK 22的纔會正式發佈,大概是在今年(2023年)9月發佈。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>21</source>
                <target>21</target>
                <compilerArgs>
                    --enable-preview
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

增加配置類:

@EnableAsync
@Configurationpublic class ThreadConfig {
    @Bean
    public AsyncTaskExecutor applicationTaskExecutor() {
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }

    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

 

前往控制器測試,打印出來就是虛擬線程了:

    @GetMapping("/name")
    public String getThreadName() {
//VirtualThread[#171]/runnable@ForkJoinPool-1-worker-4
return Thread.currentThread().toString(); } }

 

試一下JDK新提供的結構化併發工具包

package com.mycode;

import jdk.incubator.concurrent.StructuredTaskScope;

import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html
 * https://download.java.net/java/early_access/loom/docs/api/jdk.incubator.concurrent/jdk/incubator/concurrent/StructuredTaskScope.html
 */
public class StructuredConcurrency {
    static int taskNo = 10;
    static Callable<Integer> task = () -> {
        System.out.println(Thread.currentThread());
        Thread.sleep(Duration.ofSeconds(1));
        return 42;
    };

    static void simpleScope() throws InterruptedException {
        try (var scope = new StructuredTaskScope<>()) {
            scope.fork(task);
            scope.join();
        }
    }

    static void nestedScope() throws InterruptedException {
        try (var scopeOne = new StructuredTaskScope<>()) {
            Collections.nCopies(10, task).forEach(scopeOne::fork);

            try (var scopeTwo = new StructuredTaskScope<>()) {
                var scopeTwoTasks = Collections.nCopies(taskNo, task)
                        .stream().map(scopeTwo::fork).toList();
                scopeTwo.joinUntil(Instant.now().plusSeconds((long) (scopeTwoTasks.size() * 1.5)));
            } catch (TimeoutException e) {
                throw new RuntimeException(e);
            }
            scopeOne.join();
        }
    }

    static void complexScope() {
        try (var scopeGlobal = new StructuredTaskScope<>("platform", Thread.ofPlatform().factory())) {
            var foo = scopeGlobal.fork(() -> {
                try (var scopeOne = new StructuredTaskScope.ShutdownOnFailure()) {
                    var listOfFutures = Collections.nCopies(taskNo, task)
                            .stream().map(scopeOne::fork).toList();
                    scopeOne.joinUntil(Instant.now().plusSeconds((long) (listOfFutures.size() * 1.5)));
                    var results = listOfFutures.stream().map(Future::resultNow).toList();
                    System.out.println("completed in scopeOne");
                    return results;
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            });
            while (!foo.isDone()) Thread.sleep(10);

            var bar = scopeGlobal.fork(() -> foo.resultNow().parallelStream()
                    .collect(Collectors.groupingBy(Integer::valueOf, Collectors.counting())));

            scopeGlobal.join();
            bar.resultNow();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    static void unboundForkInScope() {
        try (var scopeJoinUntil = new StructuredTaskScope<>()) {
            Collections.nCopies(15, task)
                    .parallelStream().forEach(scopeJoinUntil::fork);
            scopeJoinUntil.joinUntil(Instant.now().plusSeconds(taskNo));
        } catch (WrongThreadException | TimeoutException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    static void boundStream() throws InterruptedException {
        try (var scope = new StructuredTaskScope<Integer>()) {
            for (Callable<Integer> integerCallable : Collections.nCopies(100, task)) {
                scope.fork(integerCallable);
            }
            scope.join();
        }
    }


    static void _streamInScope(boolean runParallelStream) {
        try (var scopeJoinUntil = new StructuredTaskScope<>()) {
            scopeJoinUntil.fork(() -> {
                var stream = Collections.nCopies(taskNo, task).stream();
                if (runParallelStream) {
                    stream = stream.parallel();
                }
                return stream.map(scopeJoinUntil::fork).toList();
            });
            scopeJoinUntil.join();
        } catch (WrongThreadException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    static void syncStreamInScope() {
        _streamInScope(false);
    }

    static void parallelStreamInScope() {
        _streamInScope(true);
    }

    public static void main(String[] args) throws InterruptedException {

        nestedScope();
        simpleScope();
        complexScope();
        //unboundForkInScope();

        Runnable runnable = () -> {
            System.out.println("run");
        };

        // Start a daemon thread to run a task
        Thread thread1 = Thread.ofPlatform().daemon().start(runnable);

        // Create an unstarted thread with name "duke", its start() method
        // must be invoked to schedule it to execute.
        Thread thread2 = Thread.ofPlatform().name("duke").unstarted(runnable);

        // A ThreadFactory that creates daemon threads named "worker-0", "worker-1", ...
        ThreadFactory factory = Thread.ofPlatform().daemon().name("worker-", 0).factory();

        // Start a virtual thread to run a task
        Thread thread3 = Thread.ofVirtual().start(runnable);

        // A ThreadFactory that creates virtual threads
        ThreadFactory factory1 = Thread.ofVirtual().factory();


        Thread.Builder.OfVirtual virtualThreadBuilder = Thread.ofVirtual()
                // 協程名稱
                .name("fiber-1")
                // start > 0的情況下會覆蓋name屬性配置
                .name("fiber-", 1L)
                // 是否啓用ThreadLocal
                .allowSetThreadLocals(false)
                // 是否啓用InheritableThreadLocal
                .inheritInheritableThreadLocals(false)
                // 設置未捕獲異常處理器
                .uncaughtExceptionHandler((t, e) -> {

                });

        Thread.Builder.OfPlatform platformThreadBuilder = Thread.ofPlatform()
                .daemon(true)
                .group(Thread.currentThread().getThreadGroup())
                .name("thread-1")
                .name("thread-", 1L)
                .allowSetThreadLocals(false)
                .inheritInheritableThreadLocals(false)
                .priority(1)
                .stackSize(10)
                .uncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {

                    }
                });


    }

    // 創建平臺線程建造器,對應於Thread實例
    public static Thread.Builder.OfPlatform ofPlatform() {
        return null;
    }

    // 創建虛擬線程建造器,對應於VirtualThread
    public static Thread.Builder.OfVirtual ofVirtual() {
       return null;
    }


}

 

 

參考資料:

擁抱虛擬線程 (spring.io)

Working with Virtual Threads in Spring 6 | Baeldung

 

JDK新API結構並併發工具類相關文檔:

結構性併發: https://openjdk.org/jeps/453

範圍值(類似ThreadLocal): https://openjdk.org/jeps/446

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章