最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot開(kāi)啟虛擬線程ScopedValue上下文傳遞的使用方式

 更新時(shí)間:2026年02月04日 15:59:07   作者:Demon_Hao  
本文給大家介紹了ScopedValue在Java虛擬線程中的應(yīng)用,解決了傳統(tǒng)ThreadLocal在大量短生命周期線程場(chǎng)景下的問(wèn)題,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

1. 背景

在傳統(tǒng)的 Java 應(yīng)用中,ThreadLocal 常用于在同一線程中傳遞上下文信息(如請(qǐng)求ID、用戶(hù)信息等)。
然而,隨著 Java 虛擬線程(Virtual Thread) 的引入,線程數(shù)量可以非常大(成千上萬(wàn)),ThreadLocal 在這種場(chǎng)景下存在幾個(gè)問(wèn)題:

  • 內(nèi)存泄漏風(fēng)險(xiǎn):線程長(zhǎng)期存在時(shí),ThreadLocal 變量容易被殘留引用占用。
  • 上下文傳遞復(fù)雜:虛擬線程切換可能導(dǎo)致 ThreadLocal 值不一致,尤其在使用異步或掛起操作時(shí)。

為了解決這個(gè)問(wèn)題,Java 提供了 ScopedValue,用于在虛擬線程中安全、輕量地傳遞上下文。

2. ScopedValue 特點(diǎn)

  • 輕量級(jí):與 ThreadLocal 不同,它不會(huì)在每個(gè)線程上創(chuàng)建額外的存儲(chǔ)空間。
  • 線程安全:值是不可變的,只能在創(chuàng)建的作用域內(nèi)訪問(wèn)。
  • 自動(dòng)傳遞:在虛擬線程中創(chuàng)建作用域時(shí),內(nèi)部邏輯可以自動(dòng)將上下文傳遞給掛起和恢復(fù)操作。
  • 適合虛擬線程:與 ThreadLocal 相比,ScopedValue 更適合大量短生命周期線程的場(chǎng)景。

3. 使用方式

1、全局開(kāi)啟使用虛擬線程(yaml配置)

spring:
  main:
    # 保證 JVM 在全是虛擬線程情況下不會(huì)提前退出
    keep-alive: true
  # 全局虛擬線程開(kāi)關(guān)(推薦方式)
  threads:
    virtual:
      # 啟用虛擬線程,覆蓋 TaskExecutor、@Async、@Scheduled、Web Server
      enabled: true

2、虛擬線程上下文傳遞參數(shù)

import lombok.Builder;
/**
 * 虛擬線程上下文傳遞參數(shù)
 *
 * @param traceId  鏈路ID(分布式微服務(wù)傳遞追蹤)
 * @param userId   用戶(hù)ID
 * @param tenantId 租戶(hù)ID
 */
@Builder
public record RequestContext(
    String traceId,
    String userId,
    String tenantId) {
}

3、ScopedValue工具類(lèi)

import lombok.NoArgsConstructor;
/**
 * ScopedValue工具類(lèi)
 */
@NoArgsConstructor
public final class ContextKeys {
    // 鏈路ID
    public static final String TRACE_ID = "traceId";
    /**
     * WEB請(qǐng)求上下文傳遞
     */
    public static final ScopedValue<RequestContext> REQUEST_CONTEXT = ScopedValue.newInstance();
}

4、獲取上下文業(yè)務(wù)參數(shù)

import lombok.NoArgsConstructor;
import org.slf4j.MDC;
import java.util.Optional;
import java.util.concurrent.Callable;
/**
 * 獲取上下文業(yè)務(wù)參數(shù)
 */
@NoArgsConstructor
public final class RequestContextHolder {
    /**
     * 獲取完整上下文
     */
    public static Optional<RequestContext> getOptional() {
        return ContextKeys.REQUEST_CONTEXT.isBound() ? Optional.of(ContextKeys.REQUEST_CONTEXT.get()) : Optional.empty();
    }
    /**
     * 獲取 traceId
     */
    public static String getTraceId() {
        return getOptional().map(RequestContext::traceId).orElse(null);
    }
    /**
     * 獲取 userId
     */
    public static String getUserId() {
        return getOptional().map(RequestContext::userId).orElse(null);
    }
    /**
     * 獲取 tenantId
     */
    public static String getTenantId() {
        return getOptional().map(RequestContext::tenantId).orElse(null);
    }
    /**
     * 綁定上下文并運(yùn)行 Runnable
     */
    public static void with(RequestContext ctx, Runnable task) {
        ScopedValue.where(ContextKeys.REQUEST_CONTEXT, ctx).run(() -> {
            try {
                // MDC橋接
                MDC.put(ContextKeys.TRACE_ID, ctx.traceId());
                task.run();
            } finally {
                MDC.clear();
            }
        });
    }
    /**
     * 綁定上下文并運(yùn)行 Callable
     */
    public static <T> T with(RequestContext ctx, Callable<T> task) throws Exception {
        return ScopedValue.where(ContextKeys.REQUEST_CONTEXT, ctx).call(() -> {
            try {
                // MDC橋接
                MDC.put(ContextKeys.TRACE_ID, ctx.traceId());
                return task.call();
            } finally {
                MDC.clear();
            }
        });
    }
}

5、HTTP請(qǐng)求上下文初始化

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
/**
 * HTTP請(qǐng)求上下文初始化
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextInitFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        RequestContext ctx = buildContext(request);
        // ScopedValue 綁定上下文
        try {
            ScopedValue.where(ContextKeys.REQUEST_CONTEXT, ctx).run(() -> {
                try {
                    // MDC橋接
                    MDC.put(ContextKeys.TRACE_ID, ctx.traceId());
                    filterChain.doFilter(request, response);
                } catch (IOException | ServletException e) {
                    throw new RuntimeException(e);
                } finally {
                    MDC.clear();
                }
            });
        } catch (RuntimeException e) {
            // 拆包,保持Servlet語(yǔ)義
            if (e.getCause() instanceof IOException io) throw io;
            if (e.getCause() instanceof ServletException se) throw se;
            throw e;
        }
    }
    /**
     * 構(gòu)建請(qǐng)求上下文
     */
    private RequestContext buildContext(HttpServletRequest request) {
        // 鏈路ID
        String traceId = Optional.ofNullable(request.getHeader("X-Trace-Id")).orElse(UUID.randomUUID().toString());
        // 用戶(hù)ID
        String userId = request.getHeader("X-User-Id");
        // 租戶(hù)ID
        String tenantId = request.getHeader("X-Tenant-Id");
        return RequestContext.builder().traceId(traceId).userId(userId).tenantId(tenantId).build();
    }
}

6、ScopedValue和StructuredTaskScope使用方式

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
public class DemoController {
    @GetMapping("/test")
    public String test() {
        // 獲取參數(shù)
        String tenantId = RequestContextHolder.getTenantId();
        String userId = RequestContextHolder.getUserId();
        // 新虛擬線程執(zhí)行
        RequestContext ctx = RequestContextHolder.getOptional().orElseThrow();
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        executor.submit(() -> {
            RequestContextHolder.with(ctx, () -> {
                // 異步任務(wù)中依然可以獲取 traceId / userId
                System.out.println("traceId=" + RequestContextHolder.getTraceId());
            });
        });
        return "tenantId=" + tenantId + ", userId=" + userId;
    }
    /**
     * StructuredTaskScope實(shí)現(xiàn):同步寫(xiě)法 + 并發(fā)執(zhí)行 + 自動(dòng)失敗傳播
     * 非常類(lèi)似WebFlux/Reactor的:Mono.zip(callA(), callB()).map(tuple -> combine(tuple.getT1(), tuple.getT2()));
     */
    @Transactional
    public void service() {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            var a = scope.fork(this::taskA);
            var b = scope.fork(this::taskB);
            // 等待所有
            scope.join();
            // 有失敗就拋
            scope.throwIfFailed();
            return combine(a.get(), b.get());
        }
    }
}

到此這篇關(guān)于Spring Boot開(kāi)啟虛擬線程ScopedValue上下文傳遞的文章就介紹到這了,更多相關(guān)Spring Boot開(kāi)啟虛擬線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring整合Mybatis框架方法剖析

    Spring整合Mybatis框架方法剖析

    這篇文章主要為大家介紹了Spring整合Mybatis框架方法剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • java獲取百度網(wǎng)盤(pán)真實(shí)下載鏈接的方法

    java獲取百度網(wǎng)盤(pán)真實(shí)下載鏈接的方法

    這篇文章主要介紹了java獲取百度網(wǎng)盤(pán)真實(shí)下載鏈接的方法,涉及java針對(duì)URL操作及頁(yè)面分析的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • ToStringBuilder類(lèi)的一些心得

    ToStringBuilder類(lèi)的一些心得

    ToStringBuilder類(lèi)的一些心得,需要的朋友可以參考一下
    2013-02-02
  • 使用System.exit()來(lái)優(yōu)雅地終止SpringBoot項(xiàng)目的代碼示例

    使用System.exit()來(lái)優(yōu)雅地終止SpringBoot項(xiàng)目的代碼示例

    System.exit() 方法是 Java 中用于退出程序的方法,它接受一個(gè)整數(shù)參數(shù),通常被用來(lái)指示程序的退出狀態(tài),本文給大家介紹了如何使用System.exit()來(lái)優(yōu)雅地終止SpringBoot項(xiàng)目,需要的朋友可以參考下
    2024-08-08
  • Java Spring框架簡(jiǎn)介與Spring IOC詳解

    Java Spring框架簡(jiǎn)介與Spring IOC詳解

    Spring 框架是一個(gè)輕量級(jí)的解決方案,可以一站式地構(gòu)建企業(yè)級(jí)應(yīng)用。它是為了解決 企業(yè)應(yīng)用開(kāi)發(fā)的復(fù)雜性而創(chuàng)建的。Spring 使用基本的 JavaBean 來(lái)完成以前只可能由 EJB 完成的事情。IOC 是 Inversion of Control 的縮寫(xiě),多數(shù)書(shū)籍翻譯成控制反轉(zhuǎn)
    2021-09-09
  • java為什么不建議用equals判斷對(duì)象相等

    java為什么不建議用equals判斷對(duì)象相等

    本文主要介紹了java為什么不建議用equals判斷對(duì)象相等,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 在Java中去除字符串末尾的換行符的常用方法小結(jié)

    在Java中去除字符串末尾的換行符的常用方法小結(jié)

    在日常開(kāi)發(fā)中,字符串操作是非常常見(jiàn)的需求,其中去除字符串末尾的換行符(\n)是一個(gè)很有代表性的場(chǎng)景,本文將從 Java 的幾種常用方法著手,全面解析如何去除字符串末尾的換行符,并結(jié)合代碼示例和實(shí)際使用場(chǎng)景,幫助大家掌握這一技巧,需要的朋友可以參考下
    2024-12-12
  • 解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問(wèn)題

    解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問(wèn)題

    這篇文章主要介紹了解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-11-11
  • SpringBoot+MyBatis進(jìn)行XML中循環(huán)處理List參數(shù)的終極指南

    SpringBoot+MyBatis進(jìn)行XML中循環(huán)處理List參數(shù)的終極指南

    這篇文章主要為大家詳細(xì)介紹了SpringBoot整合MyBatis進(jìn)行XML中循環(huán)處理List參數(shù)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2025-07-07
  • 使用ScheduledThreadPoolExecutor踩過(guò)最痛的坑

    使用ScheduledThreadPoolExecutor踩過(guò)最痛的坑

    這篇文章主要介紹了使用ScheduledThreadPoolExecutor踩過(guò)最痛的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08

最新評(píng)論

泰安市| 堆龙德庆县| 五河县| 台山市| 出国| 通化市| 友谊县| 合水县| 阿克| 封开县| 靖安县| 望城县| 东乡族自治县| 金华市| 铁岭市| 淅川县| 会泽县| 屏南县| 石棉县| 永州市| 潼南县| 静宁县| 清徐县| 壶关县| 习水县| 曲松县| 错那县| 扎赉特旗| 通化县| 昌吉市| 昭通市| 德江县| 且末县| 长乐市| 南开区| 万山特区| 白河县| 仁寿县| 京山县| 栾城县| 清原|