ThreadLocal與ScopedValue對比講解

1 ThreadLocal與ScopedValue

在工作中,一提到線程內(nèi)數(shù)據(jù)傳遞就想到ThreadLocal,但真正用起來卻遇到各種坑:內(nèi)存泄漏、數(shù)據(jù)污染、性能問題等等。其實,ScopedValue就像ThreadLocal的升級版,既保留了優(yōu)點,又解決了痛點。

點擊了解 ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal 用法

1.1 ThreadLocal缺點

在介紹ScopedValue之前,我們先回顧一下ThreadLocal的常見問題。其實,ThreadLocal 在設(shè)計上存在一些固有缺陷。

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

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

image.png

1.1.2 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;   
    //省去get 全參 toString
}

1.1.3 ThreadLocal問題根本原因

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

1.2 ScopedValue 新一代線程局部變量

ScopedValueJava 20中引入的預(yù)覽特性,在Java 21中成為正式特性。它旨在解決ThreadLocal的痛點,提供更安全、更高效的線程內(nèi)數(shù)據(jù)傳遞方案。

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

為了更直觀地理解ScopedValue的工作原理,參考ScopedValue的架構(gòu)圖:

image.png

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

image.png

核心優(yōu)勢:

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

技術(shù)對比:

特性 ThreadLocal ScopedValue
內(nèi)存管理 手動remove 自動管理
內(nèi)存泄漏 高風(fēng)險 無風(fēng)險
使用復(fù)雜度 高(需要try-finally) 低(結(jié)構(gòu)化綁定)
性能 較好 更優(yōu)(虛擬線程)
繼承性 需要InheritableThreadLocal 自動繼承
虛擬線程支持 有問題 完美支持

1.2.2 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;
    }
}

1.3 ScopedValue vs ThreadLocal:全面對比

如果想知道ScopedValue到底比ThreadLocal強在哪里

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

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

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


image.png

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


image.png

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


image.png

1.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) {} // 異常處理
}

1.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);
    }
}

1.4 ScopedValue高級特性

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

結(jié)構(gòu)化并發(fā):適合“并發(fā)任務(wù)管理”場景(比如一次請求內(nèi)部并行調(diào)多個服務(wù)),但是不適合作為長期復(fù)用的線程池機制,創(chuàng)建銷毀很消耗性能,不過可以用虛線程完美結(jié)合,因為 虛擬線程用完即棄,但不需要關(guān)閉

點擊了解 虛擬線程和結(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;  
    //省去get 全參
}

1.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");
            }
        });
    }
}

1.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());
        });
    }
}

1.5 實戰(zhàn)案例

用一個Web應(yīng)用中的用戶上下文管理來展示ScopedValue在真實項目中的應(yīng)用。
為了更直觀地理解Web應(yīng)用中ScopedValue的應(yīng)用,參考如下圖所示:


image.png

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


image.png

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


image.png

1.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();
}

/**
 * 用戶上下文詳細(xì)信息
 */
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;
   //省去get 全參
}

/**
 * 請求信息
 */
class RequestInfo {
    private final String requestId;
    private final String method;
    private final String path;
    private final String clientIp;
    private final Map<String, String> headers;
    //省去get 全參 
}

1.5.2 過濾器實現(xiàn)

/**
 * 認(rè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); 
        // 認(rèn)證用戶
        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());
    }
}

1.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);
    }
}

1.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();
}

1.6 遷移指南:從ThreadLocal到ScopedValue

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

1.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)閉連接
    }
}

1.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. 清理橋接層");
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容