偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

別再用ThreadLocal了,ScopedValue更香!

開發(fā) 前端
今天我們來聊聊一個即將改變我們編程習(xí)慣的新特性——ScopedValue。有些小伙伴在工作中,一提到線程內(nèi)數(shù)據(jù)傳遞就想到ThreadLocal,但真正用起來卻遇到各種坑:內(nèi)存泄漏、數(shù)據(jù)污染、性能問題等等。

前言

今天我們來聊聊一個即將改變我們編程習(xí)慣的新特性——ScopedValue。

有些小伙伴在工作中,一提到線程內(nèi)數(shù)據(jù)傳遞就想到ThreadLocal,但真正用起來卻遇到各種坑:內(nèi)存泄漏、數(shù)據(jù)污染、性能問題等等。

其實,ScopedValue就像ThreadLocal的升級版,既保留了優(yōu)點,又解決了痛點。

我們一起聊聊ScopedValue的優(yōu)勢和用法,希望對你會有所幫助。

一、ThreadLocal的痛點

在介紹ScopedValue之前,我們先回顧一下ThreadLocal的常見問題。

有些小伙伴可能會想:"ThreadLocal用得好好的,為什么要換?"

其實,ThreadLocal在設(shè)計上存在一些固有缺陷。

ThreadLocal的內(nèi)存泄漏問題

為了更直觀地理解ThreadLocal的內(nèi)存泄漏問題,我畫了一個內(nèi)存泄漏的示意圖:

圖片

ThreadLocal的典型問題代碼

/**
 * ThreadLocal典型問題演示
 */
public class ThreadLocalProblems {
    
    private static final ThreadLocal<UserContext> userContext = new ThreadLocal<>();
    private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
    
    /**
     * 問題1:內(nèi)存泄漏 - 忘記調(diào)用remove()
     */
    public void processRequest(HttpServletRequest request) {
        // 設(shè)置用戶上下文
        UserContext context = new UserContext(request.getHeader("X-User-Id"));
        userContext.set(context);
        
        try {
            // 業(yè)務(wù)處理
            businessService.process();
            
            // 問題:忘記調(diào)用 userContext.remove()
            // 在線程池中,這個線程被重用時,還會保留之前的用戶信息
        } catch (Exception e) {
            // 異常處理
        }
    }
    
    /**
     * 問題2:數(shù)據(jù)污染 - 線程復(fù)用導(dǎo)致數(shù)據(jù)混亂
     */
    public void processMultipleRequests() {
        // 線程池處理多個請求
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 10; i++) {
            finalint userId = i;
            executor.submit(() -> {
                // 設(shè)置用戶上下文
                userContext.set(new UserContext("user_" + userId));
                
                try {
                    // 模擬業(yè)務(wù)處理
                    Thread.sleep(100);
                    
                    // 問題:如果線程被復(fù)用,這里可能讀取到錯誤的用戶信息
                    String currentUser = userContext.get().getUserId();
                    System.out.println("處理用戶: " + currentUser);
                    
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    // 即使調(diào)用remove,也可能因為異常跳過
                    userContext.remove(); // 不保證一定執(zhí)行
                }
            });
        }
        
        executor.shutdown();
    }
    
    /**
     * 問題3:繼承性問題 - 子線程無法繼承父線程數(shù)據(jù)
     */
    public void parentChildThreadProblem() {
        userContext.set(new UserContext("parent_user"));
        
        Thread childThread = new Thread(() -> {
            // 這里獲取不到父線程的ThreadLocal值
            UserContext context = userContext.get(); // null
            System.out.println("子線程用戶: " + context); // 輸出null
            
            // 需要手動傳遞數(shù)據(jù)
        });
        
        childThread.start();
    }
    
    /**
     * 問題4:性能問題 - 大量ThreadLocal影響性能
     */
    public void performanceProblem() {
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < 100000; i++) {
            ThreadLocal<String> tl = new ThreadLocal<>();
            tl.set("value_" + i);
            String value = tl.get();
            tl.remove();
        }
        
        long endTime = System.currentTimeMillis();
        System.out.println("ThreadLocal操作耗時: " + (endTime - startTime) + "ms");
    }
}

/**
 * 用戶上下文
 */
class UserContext {
    private final String userId;
    private final long timestamp;
    
    public UserContext(String userId) {
        this.userId = userId;
        this.timestamp = System.currentTimeMillis();
    }
    
    public String getUserId() {
        return userId;
    }
    
    public long getTimestamp() {
        return timestamp;
    }
    
    @Override
    public String toString() {
        return"UserContext{userId='" + userId + "', timestamp=" + timestamp + "}";
    }
}

ThreadLocal問題的根本原因

  1. 生命周期管理復(fù)雜:需要手動調(diào)用set/remove,容易遺漏
  2. 內(nèi)存泄漏風(fēng)險:線程池中線程復(fù)用,Value無法被GC
  3. 繼承性差:子線程無法自動繼承父線程數(shù)據(jù)
  4. 性能開銷:ThreadLocalMap的哈希表操作有開銷

有些小伙伴可能會問:"我們用InheritableThreadLocal不就能解決繼承問題了嗎?"

我的經(jīng)驗是:InheritableThreadLocal只是緩解了問題,但帶來了新的復(fù)雜度,而且性能更差

二、ScopedValue:新一代線程局部變量

ScopedValue是Java 20中引入的預(yù)覽特性,在Java 21中成為正式特性。

它旨在解決ThreadLocal的痛點,提供更安全、更高效的線程內(nèi)數(shù)據(jù)傳遞方案。

ScopedValue的核心設(shè)計理念

為了更直觀地理解ScopedValue的工作原理,我畫了一個ScopedValue的架構(gòu)圖:

圖片圖片

ScopedValue的核心優(yōu)勢:

圖片圖片

ScopedValue基礎(chǔ)用法

/**
 * ScopedValue基礎(chǔ)用法演示
 */
public class ScopedValueBasics {
    
    // 1. 定義ScopedValue(相當(dāng)于ThreadLocal)
    private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
    private static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance();
    private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
    
    /**
     * 基礎(chǔ)用法:在作用域內(nèi)使用ScopedValue
     */
    public void basicUsage() {
        UserContext user = new UserContext("user_123");
        
        // 在作用域內(nèi)綁定值
        ScopedValue.runWhere(USER_CONTEXT, user, () -> {
            // 在這個作用域內(nèi),USER_CONTEXT.get()返回user_123
            System.out.println("當(dāng)前用戶: " + USER_CONTEXT.get().getUserId());
            
            // 可以嵌套使用
            ScopedValue.runWhere(REQUEST_ID, "req_456", () -> {
                System.out.println("請求ID: " + REQUEST_ID.get());
                System.out.println("用戶: " + USER_CONTEXT.get().getUserId());
            });
            
            // 這里REQUEST_ID已經(jīng)超出作用域,獲取會拋出異常
        });
        
        // 這里USER_CONTEXT已經(jīng)超出作用域
    }
    
    /**
     * 帶返回值的作用域
     */
    public String scopedValueWithReturn() {
        UserContext user = new UserContext("user_789");
        
        // 使用callWhere獲取返回值
        String result = ScopedValue.callWhere(USER_CONTEXT, user, () -> {
            // 業(yè)務(wù)處理
            String userId = USER_CONTEXT.get().getUserId();
            return"處理用戶: " + userId;
        });
        
        return result;
    }
    
    /**
     * 多個ScopedValue同時使用
     */
    public void multipleScopedValues() {
        UserContext user = new UserContext("user_multi");
        Connection conn = createConnection();
        
        // 同時綁定多個ScopedValue
        ScopedValue.runWhere(
            ScopedValue.where(USER_CONTEXT, user)
                      .where(DB_CONNECTION, conn)
                      .where(REQUEST_ID, "multi_req"),
            () -> {
                // 在這個作用域內(nèi)可以訪問所有綁定的值
                processBusinessLogic();
            }
        );
        
        // 作用域結(jié)束后自動清理
    }
    
    /**
     * 異常處理示例
     */
    public void exceptionHandling() {
        UserContext user = new UserContext("user_exception");
        
        try {
            ScopedValue.runWhere(USER_CONTEXT, user, () -> {
                // 業(yè)務(wù)處理
                processBusinessLogic();
                
                // 如果拋出異常,作用域也會正常結(jié)束
                if (someCondition()) {
                    thrownew RuntimeException("業(yè)務(wù)異常");
                }
            });
        } catch (RuntimeException e) {
            // 異常處理
            System.out.println("捕獲異常: " + e.getMessage());
        }
        
        // 即使發(fā)生異常,USER_CONTEXT也會自動清理
    }
    
    private Connection createConnection() {
        // 創(chuàng)建數(shù)據(jù)庫連接
        return null;
    }
    
    private void processBusinessLogic() {
        // 業(yè)務(wù)邏輯處理
        UserContext user = USER_CONTEXT.get();
        System.out.println("處理業(yè)務(wù)邏輯,用戶: " + user.getUserId());
    }
    
    private boolean someCondition() {
        return Math.random() > 0.5;
    }
}

三、ScopedValue vs ThreadLocal:全面對比

有些小伙伴可能還想知道ScopedValue到底比ThreadLocal強在哪里。

讓我們通過詳細的對比來看看。

3.1 內(nèi)存管理對比

為了更直觀地理解兩者的內(nèi)存管理差異,我畫了幾張圖做對比。

ThreadLocal的內(nèi)存模型圖:

圖片圖片

ScopedValue的內(nèi)存模型圖:

圖片圖片

二者的關(guān)鍵差異如下圖:

圖片圖片

3.2 代碼對比示例

/**
 * ThreadLocal vs ScopedValue 對比演示
 */
public class ThreadLocalVsScopedValue {
    
    // ThreadLocal方式
    private static final ThreadLocal<UserContext> TL_USER_CONTEXT = new ThreadLocal<>();
    private static final ThreadLocal<Connection> TL_CONNECTION = new ThreadLocal<>();
    
    // ScopedValue方式
    private static final ScopedValue<UserContext> SV_USER_CONTEXT = ScopedValue.newInstance();
    private static final ScopedValue<Connection> SV_CONNECTION = ScopedValue.newInstance();
    
    /**
     * ThreadLocal方式 - 傳統(tǒng)實現(xiàn)
     */
    public void processRequestThreadLocal(HttpServletRequest request) {
        // 設(shè)置上下文
        UserContext userContext = new UserContext(request.getHeader("X-User-Id"));
        TL_USER_CONTEXT.set(userContext);
        
        Connection conn = null;
        try {
            // 獲取數(shù)據(jù)庫連接
            conn = dataSource.getConnection();
            TL_CONNECTION.set(conn);
            
            // 業(yè)務(wù)處理
            processBusinessLogic();
            
        } catch (SQLException e) {
            // 異常處理
            handleException(e);
        } finally {
            // 必須手動清理 - 容易忘記!
            TL_USER_CONTEXT.remove();
            TL_CONNECTION.remove();
            
            // 關(guān)閉連接
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    // 日志記錄
                }
            }
        }
    }
    
    /**
     * ScopedValue方式 - 現(xiàn)代實現(xiàn)
     */
    public void processRequestScopedValue(HttpServletRequest request) {
        UserContext userContext = new UserContext(request.getHeader("X-User-Id"));
        
        // 使用try-with-resources管理連接
        try (Connection conn = dataSource.getConnection()) {
            
            // 在作用域內(nèi)執(zhí)行,自動管理生命周期
            ScopedValue.runWhere(
                ScopedValue.where(SV_USER_CONTEXT, userContext)
                          .where(SV_CONNECTION, conn),
                () -> {
                    // 業(yè)務(wù)處理
                    processBusinessLogic();
                }
            );
            
            // 作用域結(jié)束后自動清理,無需手動remove
        } catch (SQLException e) {
            handleException(e);
        }
    }
    
    /**
     * 業(yè)務(wù)邏輯處理 - 兩種方式對比
     */
    private void processBusinessLogic() {
        // ThreadLocal方式 - 需要處理null值
        UserContext tlUser = TL_USER_CONTEXT.get();
        if (tlUser == null) {
            throw new IllegalStateException("用戶上下文未設(shè)置");
        }
        
        Connection tlConn = TL_CONNECTION.get();
        if (tlConn == null) {
            throw new IllegalStateException("數(shù)據(jù)庫連接未設(shè)置");
        }
        
        // ScopedValue方式 - 在作用域內(nèi)保證不為null
        UserContext svUser = SV_USER_CONTEXT.get(); // 不會為null
        Connection svConn = SV_CONNECTION.get();    // 不會為null
        
        // 實際業(yè)務(wù)處理...
        System.out.println("處理用戶: " + svUser.getUserId());
    }
    
    /**
     * 線程池場景對比
     */
    public void threadPoolComparison() {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        // ThreadLocal方式 - 容易出問題
        for (int i = 0; i < 10; i++) {
            final int userId = i;
            executor.submit(() -> {
                TL_USER_CONTEXT.set(new UserContext("user_" + userId));
                try {
                    processBusinessLogic();
                } finally {
                    TL_USER_CONTEXT.remove(); // 容易忘記或異常跳過
                }
            });
        }
        
        // ScopedValue方式 - 更安全
        for (int i = 0; i < 10; i++) {
            final int userId = i;
            executor.submit(() -> {
                UserContext user = new UserContext("user_" + userId);
                ScopedValue.runWhere(SV_USER_CONTEXT, user, () -> {
                    processBusinessLogic(); // 自動管理生命周期
                });
            });
        }
        
        executor.shutdown();
    }
    
    private Connection getConnectionFromTL() {
        return TL_CONNECTION.get();
    }
    
    private DataSource dataSource = null; // 模擬數(shù)據(jù)源
    private void handleException(SQLException e) {} // 異常處理
}

3.3 性能對比測試

/**
 * 性能對比測試
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class PerformanceComparison {
    
    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
    private static final ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
    
    private static final int ITERATIONS = 100000;
    
    /**
     * ThreadLocal性能測試
     */
    @Benchmark
    public void threadLocalPerformance() {
        for (int i = 0; i < ITERATIONS; i++) {
            THREAD_LOCAL.set("value_" + i);
            String value = THREAD_LOCAL.get();
            THREAD_LOCAL.remove();
        }
    }
    
    /**
     * ScopedValue性能測試
     */
    @Benchmark
    public void scopedValuePerformance() {
        for (int i = 0; i < ITERATIONS; i++) {
            ScopedValue.runWhere(SCOPED_VALUE, "value_" + i, () -> {
                String value = SCOPED_VALUE.get();
                // 自動清理,無需remove
            });
        }
    }
    
    /**
     * 實際場景性能測試
     */
    public void realScenarioTest() {
        long tlStart = System.nanoTime();
        
        // ThreadLocal場景
        THREAD_LOCAL.set("initial_value");
        for (int i = 0; i < ITERATIONS; i++) {
            String current = THREAD_LOCAL.get();
            THREAD_LOCAL.set(current + "_" + i);
        }
        THREAD_LOCAL.remove();
        
        long tlEnd = System.nanoTime();
        
        // ScopedValue場景
        long svStart = System.nanoTime();
        
        ScopedValue.runWhere(SCOPED_VALUE, "initial_value", () -> {
            String current = SCOPED_VALUE.get();
            for (int i = 0; i < ITERATIONS; i++) {
                // ScopedValue是不可變的,需要重新綁定
                String newValue = current + "_" + i;
                ScopedValue.runWhere(SCOPED_VALUE, newValue, () -> {
                    // 嵌套作用域
                    String nestedValue = SCOPED_VALUE.get();
                });
            }
        });
        
        long svEnd = System.nanoTime();
        
        System.out.printf("ThreadLocal耗時: %d ns%n", tlEnd - tlStart);
        System.out.printf("ScopedValue耗時: %d ns%n", svEnd - svStart);
    }
}

四、ScopedValue高級特性

有些小伙伴掌握了基礎(chǔ)用法后,還想了解更高級的特性。

ScopedValue確實提供了很多強大的功能。

4.1 結(jié)構(gòu)化并發(fā)支持

ScopedValue與虛擬線程和結(jié)構(gòu)化并發(fā)完美配合:

/**
 * ScopedValue與結(jié)構(gòu)化并發(fā)
 */
public class StructuredConcurrencyExample {
    
    private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
    private static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
    
    /**
     * 結(jié)構(gòu)化并發(fā)中的ScopedValue使用
     */
    public void structuredConcurrencyWithScopedValue() throws Exception {
        UserContext user = new UserContext("structured_user");
        RequestInfo request = new RequestInfo("req_123", System.currentTimeMillis());
        
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            
            ScopedValue.runWhere(
                ScopedValue.where(USER_CONTEXT, user)
                          .where(REQUEST_INFO, request),
                () -> {
                    // 在作用域內(nèi)提交子任務(wù)
                    Future<String> userTask = scope.fork(this::fetchUserData);
                    Future<String> orderTask = scope.fork(this::fetchOrderData);
                    Future<String> paymentTask = scope.fork(this::fetchPaymentData);
                    
                    try {
                        // 等待所有任務(wù)完成
                        scope.join();
                        scope.throwIfFailed();
                        
                        // 處理結(jié)果
                        String userData = userTask.resultNow();
                        String orderData = orderTask.resultNow();
                        String paymentData = paymentTask.resultNow();
                        
                        System.out.println("聚合結(jié)果: " + userData + ", " + orderData + ", " + paymentData);
                        
                    } catch (InterruptedException | ExecutionException e) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("任務(wù)執(zhí)行失敗", e);
                    }
                }
            );
        }
    }
    
    private String fetchUserData() {
        // 可以訪問ScopedValue,無需參數(shù)傳遞
        UserContext user = USER_CONTEXT.get();
        RequestInfo request = REQUEST_INFO.get();
        
        return "用戶數(shù)據(jù): " + user.getUserId() + ", 請求: " + request.getRequestId();
    }
    
    private String fetchOrderData() {
        UserContext user = USER_CONTEXT.get();
        return "訂單數(shù)據(jù): " + user.getUserId();
    }
    
    private String fetchPaymentData() {
        UserContext user = USER_CONTEXT.get();
        return "支付數(shù)據(jù): " + user.getUserId();
    }
}

class RequestInfo {
    private final String requestId;
    private final long timestamp;
    
    public RequestInfo(String requestId, long timestamp) {
        this.requestId = requestId;
        this.timestamp = timestamp;
    }
    
    public String getRequestId() { return requestId; }
    public long getTimestamp() { return timestamp; }
}

4.2 繼承和嵌套作用域

/**
 * ScopedValue繼承和嵌套
 */
public class ScopedValueInheritance {
    
    private static final ScopedValue<String> PARENT_VALUE = ScopedValue.newInstance();
    private static final ScopedValue<String> CHILD_VALUE = ScopedValue.newInstance();
    
    /**
     * 作用域嵌套
     */
    public void nestedScopes() {
        ScopedValue.runWhere(PARENT_VALUE, "parent_value", () -> {
            System.out.println("外層作用域: " + PARENT_VALUE.get());
            
            // 內(nèi)層作用域可以訪問外層值
            ScopedValue.runWhere(CHILD_VALUE, "child_value", () -> {
                System.out.println("內(nèi)層作用域 - 父值: " + PARENT_VALUE.get());
                System.out.println("內(nèi)層作用域 - 子值: " + CHILD_VALUE.get());
                
                // 可以重新綁定父值(遮蔽)
                ScopedValue.runWhere(PARENT_VALUE, "shadowed_parent", () -> {
                    System.out.println("遮蔽作用域 - 父值: " + PARENT_VALUE.get());
                    System.out.println("遮蔽作用域 - 子值: " + CHILD_VALUE.get());
                });
                
                // 恢復(fù)原來的父值
                System.out.println("恢復(fù)作用域 - 父值: " + PARENT_VALUE.get());
            });
            
            // 子值已超出作用域
            try {
                System.out.println(CHILD_VALUE.get()); // 拋出異常
            } catch (Exception e) {
                System.out.println("子值已超出作用域: " + e.getMessage());
            }
        });
    }
    
    /**
     * 虛擬線程中的繼承
     */
    public void virtualThreadInheritance() throws Exception {
        ScopedValue.runWhere(PARENT_VALUE, "virtual_parent", () -> {
            try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
                
                // 虛擬線程自動繼承ScopedValue
                for (int i = 0; i < 3; i++) {
                    final int taskId = i;
                    scope.fork(() -> {
                        // 可以訪問父線程的ScopedValue
                        String parentVal = PARENT_VALUE.get();
                        return "任務(wù)" + taskId + " - 父值: " + parentVal;
                    });
                }
                
                scope.join();
                scope.throwIfFailed();
            }
        });
    }
    
    /**
     * 條件綁定
     */
    public void conditionalBinding() {
        String condition = Math.random() > 0.5 ? "case_a" : "case_b";
        
        ScopedValue.runWhere(PARENT_VALUE, condition, () -> {
            String value = PARENT_VALUE.get();
            
            if ("case_a".equals(value)) {
                System.out.println("處理情況A");
            } else {
                System.out.println("處理情況B");
            }
        });
    }
}

4.3 錯誤處理和調(diào)試

/**
 * ScopedValue錯誤處理和調(diào)試
 */
public class ScopedValueErrorHandling {
    
    private static final ScopedValue<String> MAIN_VALUE = ScopedValue.newInstance();
    private static final ScopedValue<Integer> COUNT_VALUE = ScopedValue.newInstance();
    
    /**
     * 異常處理
     */
    public void exceptionHandling() {
        try {
            ScopedValue.runWhere(MAIN_VALUE, "test_value", () -> {
                // 業(yè)務(wù)邏輯
                processWithError();
            });
        } catch (RuntimeException e) {
            System.out.println("捕獲異常: " + e.getMessage());
            // ScopedValue已自動清理,無需額外處理
        }
        
        // 驗證值已清理
        try {
            String value = MAIN_VALUE.get();
            System.out.println("不應(yīng)該執(zhí)行到這里: " + value);
        } catch (Exception e) {
            System.out.println("值已正確清理: " + e.getMessage());
        }
    }
    
    /**
     * 調(diào)試信息
     */
    public void debugInformation() {
        ScopedValue.runWhere(
            ScopedValue.where(MAIN_VALUE, "debug_value")
                      .where(COUNT_VALUE, 42),
            () -> {
                // 獲取當(dāng)前綁定的所有ScopedValue
                System.out.println("當(dāng)前作用域綁定:");
                System.out.println("MAIN_VALUE: " + MAIN_VALUE.get());
                System.out.println("COUNT_VALUE: " + COUNT_VALUE.get());
                
                // 模擬復(fù)雜調(diào)試
                debugComplexScenario();
            }
        );
    }
    
    /**
     * 資源清理保證
     */
    public void resourceCleanupGuarantee() {
        List<String> cleanupLog = new ArrayList<>();
        
        ScopedValue.runWhere(MAIN_VALUE, "resource_value", () -> {
            // 注冊清理鉤子
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                cleanupLog.add("資源清理完成");
            }));
            
            // 即使這里發(fā)生異常,ScopedValue也會清理
            if (Math.random() > 0.5) {
                throw new RuntimeException("模擬異常");
            }
        });
        
        // 檢查清理情況
        System.out.println("清理日志: " + cleanupLog);
    }
    
    private void processWithError() {
        throw new RuntimeException("業(yè)務(wù)處理異常");
    }
    
    private void debugComplexScenario() {
        // 復(fù)雜的調(diào)試場景
        ScopedValue.runWhere(COUNT_VALUE, COUNT_VALUE.get() + 1, () -> {
            System.out.println("嵌套調(diào)試 - COUNT_VALUE: " + COUNT_VALUE.get());
        });
    }
}

五、實戰(zhàn)案例

有些小伙伴可能還想看更復(fù)雜的實戰(zhàn)案例。

讓我們用一個Web應(yīng)用中的用戶上下文管理來展示ScopedValue在真實項目中的應(yīng)用。

為了更直觀地理解Web應(yīng)用中ScopedValue的應(yīng)用,我畫了一個請求處理流程的架構(gòu)圖:

圖片圖片

ScopedValue的生命周期如下圖所示:

圖片圖片

優(yōu)勢如下圖所示:

圖片圖片

5.1 定義Web應(yīng)用中的ScopedValue

/**
 * Web應(yīng)用ScopedValue定義
 */
public class WebScopedValues {
    
    // 用戶上下文
    public static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
    
    // 請求信息
    public static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
    
    // 數(shù)據(jù)庫連接(可選)
    public static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance();
    
    // 追蹤ID
    public static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();
}

/**
 * 用戶上下文詳細信息
 */
class UserContext {
    private final String userId;
    private final String username;
    private final List<String> roles;
    private final Map<String, Object> attributes;
    private final Locale locale;
    
    public UserContext(String userId, String username, List<String> roles, 
                      Map<String, Object> attributes, Locale locale) {
        this.userId = userId;
        this.username = username;
        this.roles = Collections.unmodifiableList(new ArrayList<>(roles));
        this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes));
        this.locale = locale;
    }
    
    // Getter方法
    public String getUserId() { return userId; }
    public String getUsername() { return username; }
    public List<String> getRoles() { return roles; }
    public Map<String, Object> getAttributes() { return attributes; }
    public Locale getLocale() { return locale; }
    
    public boolean hasRole(String role) {
        return roles.contains(role);
    }
    
    public Object getAttribute(String key) {
        return attributes.get(key);
    }
}

/**
 * 請求信息
 */
class RequestInfo {
    private final String requestId;
    private final String method;
    private final String path;
    private final String clientIp;
    private final Map<String, String> headers;
    
    public RequestInfo(String requestId, String method, String path, 
                      String clientIp, Map<String, String> headers) {
        this.requestId = requestId;
        this.method = method;
        this.path = path;
        this.clientIp = clientIp;
        this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
    }
    
    // Getter方法
    public String getRequestId() { return requestId; }
    public String getMethod() { return method; }
    public String getPath() { return path; }
    public String getClientIp() { return clientIp; }
    public Map<String, String> getHeaders() { return headers; }
}

5.2 過濾器實現(xiàn)

/**
 * 認證過濾器 - 使用ScopedValue
 */
@Component
@Slf4j
public class AuthenticationFilter implements Filter {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 生成請求ID
        String requestId = generateRequestId();
        
        // 提取請求信息
        RequestInfo requestInfo = extractRequestInfo(httpRequest, requestId);
        
        // 認證用戶
        UserContext userContext = authenticateUser(httpRequest);
        
        // 在作用域內(nèi)執(zhí)行請求處理
        ScopedValue.runWhere(
            ScopedValue.where(WebScopedValues.REQUEST_INFO, requestInfo)
                      .where(WebScopedValues.USER_CONTEXT, userContext)
                      .where(WebScopedValues.TRACE_ID, requestId),
            () -> {
                try {
                    chain.doFilter(request, response);
                } catch (Exception e) {
                    log.error("請求處理異常", e);
                    throw new RuntimeException("過濾器異常", e);
                 }
            }
        );
        
        // 作用域結(jié)束后自動清理所有ScopedValue
        log.info("請求處理完成: {}", requestId);
    }
    
    private String generateRequestId() {
        return "req_" + System.currentTimeMillis() + "_" + ThreadLocalRandom.current().nextInt(1000, 9999);
    }
    
    private RequestInfo extractRequestInfo(HttpServletRequest request, String requestId) {
        Map<String, String> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        
        return new RequestInfo(
            requestId,
            request.getMethod(),
            request.getRequestURI(),
            request.getRemoteAddr(),
            headers
        );
    }
    
    private UserContext authenticateUser(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            return tokenProvider.validateToken(token);
        }
        
        // 返回 匿名用戶
        return new UserContext(
            "anonymous",
            "Anonymous User",
            List.of("GUEST"),
            Map.of("source", "web"),
            request.getLocale()
        );
    }
}

5.3 業(yè)務(wù)層使用

/**
 * 用戶服務(wù) - 使用ScopedValue
 */
@Service
@Slf4j
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 獲取當(dāng)前用戶信息
     */
    public UserProfile getCurrentUserProfile() {
        UserContext userContext = WebScopedValues.USER_CONTEXT.get();
        RequestInfo requestInfo = WebScopedValues.REQUEST_INFO.get();
        String traceId = WebScopedValues.TRACE_ID.get();
        
        log.info("[{}] 獲取用戶資料: {}", traceId, userContext.getUserId());
        
        // 根據(jù)用戶ID查詢用戶信息
        User user = userRepository.findById(userContext.getUserId())
            .orElseThrow(() -> new UserNotFoundException("用戶不存在: " + userContext.getUserId()));
        
        // 構(gòu)建用戶資料
        return UserProfile.builder()
            .userId(user.getId())
            .username(user.getUsername())
            .email(user.getEmail())
            .roles(userContext.getRoles())
            .locale(userContext.getLocale())
            .lastLogin(user.getLastLoginTime())
            .build();
    }
    
    /**
     * 更新用戶信息
     */
    public void updateUserProfile(UpdateProfileRequest request) {
        UserContext userContext = WebScopedValues.USER_CONTEXT.get();
        String traceId = WebScopedValues.TRACE_ID.get();
        
        log.info("[{}] 更新用戶資料: {}", traceId, userContext.getUserId());
        
        // 驗證權(quán)限
        if (!userContext.getUserId().equals(request.getUserId())) {
            throw new PermissionDeniedException("無權(quán)更新其他用戶資料");
        }
        
        // 更新用戶信息
        User user = userRepository.findById(request.getUserId())
            .orElseThrow(() -> new UserNotFoundException("用戶不存在: " + request.getUserId()));
        
        user.setEmail(request.getEmail());
        user.setUpdateTime(LocalDateTime.now());
        
        userRepository.save(user);
        
        log.info("[{}] 用戶資料更新成功: {}", traceId, userContext.getUserId());
    }
    
    /**
     * 獲取用戶訂單列表
     */
    public List<Order> getUserOrders() {
        UserContext userContext = WebScopedValues.USER_CONTEXT.get();
        
        // 調(diào)用訂單服務(wù),無需傳遞用戶ID
        return orderService.getUserOrders();
    }
}

/**
 * 訂單服務(wù)
 */
@Service
@Slf4j
@Transactional
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    public List<Order> getUserOrders() {
        UserContext userContext = WebScopedValues.USER_CONTEXT.get();
        String traceId = WebScopedValues.TRACE_ID.get();
        
        log.info("[{}] 查詢用戶訂單: {}", traceId, userContext.getUserId());
        
        // 直接從ScopedValue獲取用戶ID,無需參數(shù)傳遞
        return orderRepository.findByUserId(userContext.getUserId());
    }
    
    /**
     * 創(chuàng)建訂單
     */
    public Order createOrder(CreateOrderRequest request) {
        UserContext userContext = WebScopedValues.USER_CONTEXT.get();
        String traceId = WebScopedValues.TRACE_ID.get();
        
        log.info("[{}] 創(chuàng)建訂單: 用戶={}", traceId, userContext.getUserId());
        
        // 創(chuàng)建訂單
        Order order = new Order();
        order.setOrderId(generateOrderId());
        order.setUserId(userContext.getUserId());
        order.setAmount(request.getTotalAmount());
        order.setStatus(OrderStatus.CREATED);
        order.setCreateTime(LocalDateTime.now());
        
        Order savedOrder = orderRepository.save(order);
        
        log.info("[{}] 訂單創(chuàng)建成功: {}", traceId, savedOrder.getOrderId());
        
        return savedOrder;
    }
    
    private String generateOrderId() {
        return "ORD" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000, 9999);
    }
}

5.4 Controller層

/**
 * 用戶控制器 - 使用ScopedValue
 */
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
    
    @Autowired
    private UserService userService;
    
    /**
     * 獲取當(dāng)前用戶資料
     */
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getCurrentUserProfile() {
        // 無需傳遞用戶ID,直接從ScopedValue獲取
        UserProfile profile = userService.getCurrentUserProfile();
        return ResponseEntity.ok(profile);
    }
    
    /**
     * 更新用戶資料
     */
    @PutMapping("/profile")
    public ResponseEntity<Void> updateUserProfile(@RequestBody @Valid UpdateProfileRequest request) {
        userService.updateUserProfile(request);
        return ResponseEntity.ok().build();
    }
    
    /**
     * 獲取用戶訂單
     */
    @GetMapping("/orders")
    public ResponseEntity<List<Order>> getUserOrders() {
        List<Order> orders = userService.getUserOrders();
        return ResponseEntity.ok(orders);
    }
    
    /**
     * 異常處理
     */
    @ExceptionHandler({UserNotFoundException.class, PermissionDeniedException.class})
    public ResponseEntity<ErrorResponse> handleUserExceptions(RuntimeException e) {
        // 可以從ScopedValue獲取請求信息用于日志
        String traceId = WebScopedValues.TRACE_ID.get();
        log.error("[{}] 用戶操作異常: {}", traceId, e.getMessage());
        
        ErrorResponse error = new ErrorResponse(
            e.getClass().getSimpleName(),
            e.getMessage(),
            traceId
        );
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}

/**
 * 錯誤響應(yīng)
 */
@Data
@AllArgsConstructor
class ErrorResponse {
    private String error;
    private String message;
    private String traceId;
    private long timestamp = System.currentTimeMillis();
}

六、遷移指南:從ThreadLocal到ScopedValue

有些小伙伴可能擔(dān)心遷移成本,其實從ThreadLocal遷移到ScopedValue并不復(fù)雜。

6.1 遷移步驟

/**
 * ThreadLocal到ScopedValue遷移指南
 */
public class MigrationGuide {
    
    // ThreadLocal定義(舊方式)
    private static final ThreadLocal<UserContext> TL_USER = new ThreadLocal<>();
    private static final ThreadLocal<Connection> TL_CONN = new ThreadLocal<>();
    private static final ThreadLocal<String> TL_TRACE = new ThreadLocal<>();
    
    // ScopedValue定義(新方式)
    private static final ScopedValue<UserContext> SV_USER = ScopedValue.newInstance();
    private static final ScopedValue<Connection> SV_CONN = ScopedValue.newInstance();
    private static final ScopedValue<String> SV_TRACE = ScopedValue.newInstance();
    
    /**
     * 遷移前:ThreadLocal方式
     */
    public void beforeMigration() {
        // 設(shè)置值
        TL_USER.set(new UserContext("user_old"));
        TL_TRACE.set("trace_old");
        
        Connection conn = null;
        try {
            conn = createConnection();
            TL_CONN.set(conn);
            
            // 業(yè)務(wù)處理
            processBusinessOld();
            
        } catch (Exception e) {
            // 異常處理
        } finally {
            // 必須手動清理
            TL_USER.remove();
            TL_TRACE.remove();
            TL_CONN.remove();
            
            if (conn != null) {
                closeConnection(conn);
            }
        }
    }
    
    /**
     * 遷移后:ScopedValue方式
     */
    public void afterMigration() {
        UserContext user = new UserContext("user_new");
        String trace = "trace_new";
        
        // 使用try-with-resources管理連接
        try (Connection conn = createConnection()) {
            
            // 在作用域內(nèi)執(zhí)行
            ScopedValue.runWhere(
                ScopedValue.where(SV_USER, user)
                          .where(SV_TRACE, trace)
                          .where(SV_CONN, conn),
                () -> {
                    // 業(yè)務(wù)處理
                    processBusinessNew();
                }
            );
            
            // 自動清理,無需finally塊
        } catch (Exception e) {
            // 異常處理
        }
    }
    
    /**
     * 業(yè)務(wù)處理 - 舊方式
     */
    private void processBusinessOld() {
        // 需要處理null值
        UserContext user = TL_USER.get();
        if (user == null) {
            thrownew IllegalStateException("用戶上下文未設(shè)置");
        }
        
        Connection conn = TL_CONN.get();
        if (conn == null) {
            thrownew IllegalStateException("數(shù)據(jù)庫連接未設(shè)置");
        }
        
        String trace = TL_TRACE.get();
        
        // 業(yè)務(wù)邏輯...
        System.out.println("處理用戶: " + user.getUserId() + ", 追蹤: " + trace);
    }
    
    /**
     * 業(yè)務(wù)處理 - 新方式
     */
    private void processBusinessNew() {
        // 在作用域內(nèi)保證不為null
        UserContext user = SV_USER.get();
        Connection conn = SV_CONN.get();
        String trace = SV_TRACE.get();
        
        // 業(yè)務(wù)邏輯...
        System.out.println("處理用戶: " + user.getUserId() + ", 追蹤: " + trace);
    }
    
    /**
     * 復(fù)雜遷移場景:嵌套ThreadLocal
     */
    public void complexMigration() {
        // 舊方式:嵌套ThreadLocal
        TL_USER.set(new UserContext("outer_user"));
        try {
            // 內(nèi)層邏輯
            TL_USER.set(new UserContext("inner_user"));
            try {
                processBusinessOld();
            } finally {
                // 恢復(fù)外層值
                TL_USER.set(new UserContext("outer_user"));
            }
        } finally {
            TL_USER.remove();
        }
        
        // 新方式:嵌套ScopedValue
        ScopedValue.runWhere(SV_USER, new UserContext("outer_user"), () -> {
            ScopedValue.runWhere(SV_USER, new UserContext("inner_user"), () -> {
                processBusinessNew(); // 使用內(nèi)層值
            });
            // 自動恢復(fù)外層值
            processBusinessNew(); // 使用外層值
        });
    }
    
    private Connection createConnection() {
        // 創(chuàng)建連接
        return null;
    }
    
    private void closeConnection(Connection conn) {
        // 關(guān)閉連接
    }
}

6.2 兼容性處理

/**
 * 兼容性處理 - 逐步遷移
 */
public class CompatibilityLayer {
    
    // 新代碼使用ScopedValue
    private static final ScopedValue<UserContext> SV_USER = ScopedValue.newInstance();
    
    // 舊代碼可能還在使用ThreadLocal
    private static final ThreadLocal<UserContext> TL_USER = new ThreadLocal<>();
    
    /**
     * 橋接模式:同時支持兩種方式
     */
    public void bridgePattern() {
        UserContext user = new UserContext("bridge_user");
        
        // 在新作用域內(nèi)執(zhí)行
        ScopedValue.runWhere(SV_USER, user, () -> {
            // 同時設(shè)置ThreadLocal以兼容舊代碼
            TL_USER.set(user);
            
            try {
                // 執(zhí)行業(yè)務(wù)邏輯(新舊代碼都可以工作)
                processMixedBusiness();
                
            } finally {
                // 清理ThreadLocal
                TL_USER.remove();
            }
        });
    }
    
    /**
     * 適配器:讓舊代碼使用ScopedValue
     */
    public static class ThreadLocalAdapter {
        private final ScopedValue<UserContext> scopedValue;
        
        public ThreadLocalAdapter(ScopedValue<UserContext> scopedValue) {
            this.scopedValue = scopedValue;
        }
        
        public void set(UserContext user) {
            // 對于set操作,需要在適當(dāng)?shù)淖饔糜蛘{(diào)用
            throw new UnsupportedOperationException("請使用ScopedValue.runWhere");
        }
        
        public UserContext get() {
            try {
                return scopedValue.get();
            } catch (Exception e) {
                // 如果不在作用域內(nèi),返回null(模擬ThreadLocal行為)
                returnnull;
            }
        }
        
        public void remove() {
            // 無需操作,ScopedValue自動管理
        }
    }
    
    /**
     * 混合業(yè)務(wù)處理
     */
    private void processMixedBusiness() {
        // 新代碼使用ScopedValue
        UserContext svUser = SV_USER.get();
        System.out.println("ScopedValue用戶: " + svUser.getUserId());
        
        // 舊代碼使用ThreadLocal(通過橋接設(shè)置)
        UserContext tlUser = TL_USER.get();
        System.out.println("ThreadLocal用戶: " + tlUser.getUserId());
        
        // 兩者應(yīng)該相同
        assert svUser == tlUser;
    }
    
    /**
     * 逐步遷移策略
     */
    public void gradualMigrationStrategy() {
        // 階段1:引入ScopedValue,與ThreadLocal共存
        // 階段2:新代碼使用ScopedValue,舊代碼逐步遷移
        // 階段3:移除ThreadLocal,完全使用ScopedValue
        
        System.out.println("建議的遷移階段:");
        System.out.println("1. 引入ScopedValue,建立橋接");
        System.out.println("2. 新功能使用ScopedValue");
        System.out.println("3. 逐步遷移舊代碼");
        System.out.println("4. 移除ThreadLocal相關(guān)代碼");
        System.out.println("5. 清理橋接層");
    }
}

總結(jié)

經(jīng)過上面的深度剖析,我們來總結(jié)一下ScopedValue的核心優(yōu)勢。

核心優(yōu)勢

  1. 內(nèi)存安全:自動生命周期管理,徹底解決內(nèi)存泄漏
  2. 使用簡單:結(jié)構(gòu)化綁定,無需手動清理
  3. 性能優(yōu)異:專為虛擬線程優(yōu)化,性能更好
  4. 并發(fā)友好:完美支持結(jié)構(gòu)化并發(fā)和虛擬線程

遷移決策指南

有些小伙伴在遷移時可能猶豫不決,我總結(jié)了一個決策指南:


ThreadLocal vs ScopedValue 選擇口訣

  • 新項目:直接使用ScopedValue,享受現(xiàn)代特性
  • 老項目:逐步遷移,先在新模塊使用
  • 性能敏感:ScopedValue在虛擬線程中表現(xiàn)更佳
  • 內(nèi)存敏感:ScopedValue無內(nèi)存泄漏風(fēng)險
  • 團隊技能:ThreadLocal更普及,ScopedValue需要學(xué)習(xí)

技術(shù)對比

特性

ThreadLocal

ScopedValue

內(nèi)存管理

手動remove

自動管理

內(nèi)存泄漏

高風(fēng)險

無風(fēng)險

使用復(fù)雜度

高(需要try-finally)

低(結(jié)構(gòu)化綁定)

性能

較好

更優(yōu)(虛擬線程)

繼承性

需要InheritableThreadLocal

自動繼承

虛擬線程支持

有問題

完美支持

最后的建議

ScopedValue是Java并發(fā)編程的重要進步,我建議大家:

  1. 學(xué)習(xí)掌握:盡快學(xué)習(xí)掌握ScopedValue的使用
  2. 新項目首選:在新項目中優(yōu)先使用ScopedValue
  3. 逐步遷移:在老項目中制定合理的遷移計劃
  4. 關(guān)注生態(tài):關(guān)注相關(guān)框架對ScopedValue的支持
責(zé)任編輯:武曉燕 來源: 蘇三說技術(shù)
相關(guān)推薦

2025-06-25 09:31:41

2020-12-02 11:18:50

print調(diào)試代碼Python

2025-08-13 03:00:00

2025-05-19 04:00:00

2020-12-04 10:05:00

Pythonprint代碼

2021-06-09 06:41:11

OFFSETLIMIT分頁

2020-12-15 08:06:45

waitnotifyCondition

2021-01-29 11:05:50

PrintPython代碼

2020-12-03 09:05:38

SQL代碼方案

2023-10-26 16:33:59

float 布局前段CSS

2021-05-25 09:30:44

kill -9Linux kill -9 pid

2020-07-17 07:15:38

數(shù)據(jù)庫ID代碼

2022-01-27 07:48:37

虛擬項目Django

2024-12-26 07:47:20

2024-06-12 13:54:37

編程語言字符串代碼

2019-03-12 14:48:29

路由器XBOXPS4

2022-10-27 21:34:28

數(shù)據(jù)庫機器學(xué)習(xí)架構(gòu)

2025-05-15 03:00:00

2022-03-10 10:12:04

自動化腳本Bash

2025-08-06 09:31:12

點贊
收藏

51CTO技術(shù)棧公眾號