<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      再見(jiàn)了ThreadLocal,我決定用ScopedValue!

      前言

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

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

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

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

      一、ThreadLocal的痛點(diǎn)

      在介紹ScopedValue之前,我們先回顧一下ThreadLocal的常見(jiàn)問(wèn)題。

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

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

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

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

      image

      ThreadLocal的典型問(wèn)題代碼

      /**
       * ThreadLocal典型問(wèn)題演示
       */
      public class ThreadLocalProblems {
          
          private static final ThreadLocal<UserContext> userContext = new ThreadLocal<>();
          private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
          
          /**
           * 問(wèn)題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();
                  
                  // 問(wèn)題:忘記調(diào)用 userContext.remove()
                  // 在線程池中,這個(gè)線程被重用時(shí),還會(huì)保留之前的用戶信息
              } catch (Exception e) {
                  // 異常處理
              }
          }
          
          /**
           * 問(wèn)題2:數(shù)據(jù)污染 - 線程復(fù)用導(dǎo)致數(shù)據(jù)混亂
           */
          public void processMultipleRequests() {
              // 線程池處理多個(gè)請(qǐng)求
              ExecutorService executor = Executors.newFixedThreadPool(5);
              
              for (int i = 0; i < 10; i++) {
                  final int userId = i;
                  executor.submit(() -> {
                      // 設(shè)置用戶上下文
                      userContext.set(new UserContext("user_" + userId));
                      
                      try {
                          // 模擬業(yè)務(wù)處理
                          Thread.sleep(100);
                          
                          // 問(wèn)題:如果線程被復(fù)用,這里可能讀取到錯(cuò)誤的用戶信息
                          String currentUser = userContext.get().getUserId();
                          System.out.println("處理用戶: " + currentUser);
                          
                      } catch (InterruptedException e) {
                          Thread.currentThread().interrupt();
                      } finally {
                          // 即使調(diào)用remove,也可能因?yàn)楫惓L^(guò)
                          userContext.remove(); // 不保證一定執(zhí)行
                      }
                  });
              }
              
              executor.shutdown();
          }
          
          /**
           * 問(wèn)題3:繼承性問(wèn)題 - 子線程無(wú)法繼承父線程數(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
                  
                  // 需要手動(dòng)傳遞數(shù)據(jù)
              });
              
              childThread.start();
          }
          
          /**
           * 問(wèn)題4:性能問(wèn)題 - 大量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操作耗時(shí): " + (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問(wèn)題的根本原因

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

      有些小伙伴可能會(huì)問(wèn):"我們用InheritableThreadLocal不就能解決繼承問(wèn)題了嗎?"

      我的經(jīng)驗(yàn)是:InheritableThreadLocal只是緩解了問(wèn)題,但帶來(lái)了新的復(fù)雜度,而且性能更差。

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

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

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

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

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

      image

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

      image

      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, () -> {
                  // 在這個(gè)作用域內(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("請(qǐng)求ID: " + REQUEST_ID.get());
                      System.out.println("用戶: " + USER_CONTEXT.get().getUserId());
                  });
                  
                  // 這里REQUEST_ID已經(jīng)超出作用域,獲取會(huì)拋出異常
              });
              
              // 這里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;
          }
          
          /**
           * 多個(gè)ScopedValue同時(shí)使用
           */
          public void multipleScopedValues() {
              UserContext user = new UserContext("user_multi");
              Connection conn = createConnection();
              
              // 同時(shí)綁定多個(gè)ScopedValue
              ScopedValue.runWhere(
                  ScopedValue.where(USER_CONTEXT, user)
                            .where(DB_CONNECTION, conn)
                            .where(REQUEST_ID, "multi_req"),
                  () -> {
                      // 在這個(gè)作用域內(nèi)可以訪問(wèn)所有綁定的值
                      processBusinessLogic();
                  }
              );
              
              // 作用域結(jié)束后自動(dòng)清理
          }
          
          /**
           * 異常處理示例
           */
          public void exceptionHandling() {
              UserContext user = new UserContext("user_exception");
              
              try {
                  ScopedValue.runWhere(USER_CONTEXT, user, () -> {
                      // 業(yè)務(wù)處理
                      processBusinessLogic();
                      
                      // 如果拋出異常,作用域也會(huì)正常結(jié)束
                      if (someCondition()) {
                          throw new RuntimeException("業(yè)務(wù)異常");
                      }
                  });
              } catch (RuntimeException e) {
                  // 異常處理
                  System.out.println("捕獲異常: " + e.getMessage());
              }
              
              // 即使發(fā)生異常,USER_CONTEXT也會(huì)自動(dòng)清理
          }
          
          private Connection createConnection() {
              // 創(chuàng)建數(shù)據(jù)庫(kù)連接
              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:全面對(duì)比

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

      讓我們通過(guò)詳細(xì)的對(duì)比來(lái)看看。

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

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

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

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

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

      3.2 代碼對(duì)比示例

      /**
       * ThreadLocal vs ScopedValue 對(duì)比演示
       */
      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)實(shí)現(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ù)庫(kù)連接
                  conn = dataSource.getConnection();
                  TL_CONNECTION.set(conn);
                  
                  // 業(yè)務(wù)處理
                  processBusinessLogic();
                  
              } catch (SQLException e) {
                  // 異常處理
                  handleException(e);
              } finally {
                  // 必須手動(dòng)清理 - 容易忘記!
                  TL_USER_CONTEXT.remove();
                  TL_CONNECTION.remove();
                  
                  // 關(guān)閉連接
                  if (conn != null) {
                      try {
                          conn.close();
                      } catch (SQLException e) {
                          // 日志記錄
                      }
                  }
              }
          }
          
          /**
           * ScopedValue方式 - 現(xiàn)代實(shí)現(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í)行,自動(dòng)管理生命周期
                  ScopedValue.runWhere(
                      ScopedValue.where(SV_USER_CONTEXT, userContext)
                                .where(SV_CONNECTION, conn),
                      () -> {
                          // 業(yè)務(wù)處理
                          processBusinessLogic();
                      }
                  );
                  
                  // 作用域結(jié)束后自動(dòng)清理,無(wú)需手動(dòng)remove
              } catch (SQLException e) {
                  handleException(e);
              }
          }
          
          /**
           * 業(yè)務(wù)邏輯處理 - 兩種方式對(duì)比
           */
          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ù)庫(kù)連接未設(shè)置");
              }
              
              // ScopedValue方式 - 在作用域內(nèi)保證不為null
              UserContext svUser = SV_USER_CONTEXT.get(); // 不會(huì)為null
              Connection svConn = SV_CONNECTION.get();    // 不會(huì)為null
              
              // 實(shí)際業(yè)務(wù)處理...
              System.out.println("處理用戶: " + svUser.getUserId());
          }
          
          /**
           * 線程池場(chǎng)景對(duì)比
           */
          public void threadPoolComparison() {
              ExecutorService executor = Executors.newFixedThreadPool(5);
              
              // ThreadLocal方式 - 容易出問(wèn)題
              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(); // 容易忘記或異常跳過(guò)
                      }
                  });
              }
              
              // 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(); // 自動(dòng)管理生命周期
                      });
                  });
              }
              
              executor.shutdown();
          }
          
          private Connection getConnectionFromTL() {
              return TL_CONNECTION.get();
          }
          
          private DataSource dataSource = null; // 模擬數(shù)據(jù)源
          private void handleException(SQLException e) {} // 異常處理
      }
      

      3.3 性能對(duì)比測(cè)試

      /**
       * 性能對(duì)比測(cè)試
       */
      @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性能測(cè)試
           */
          @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性能測(cè)試
           */
          @Benchmark
          public void scopedValuePerformance() {
              for (int i = 0; i < ITERATIONS; i++) {
                  ScopedValue.runWhere(SCOPED_VALUE, "value_" + i, () -> {
                      String value = SCOPED_VALUE.get();
                      // 自動(dòng)清理,無(wú)需remove
                  });
              }
          }
          
          /**
           * 實(shí)際場(chǎng)景性能測(cè)試
           */
          public void realScenarioTest() {
              long tlStart = System.nanoTime();
              
              // ThreadLocal場(chǎng)景
              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場(chǎng)景
              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耗時(shí): %d ns%n", tlEnd - tlStart);
              System.out.printf("ScopedValue耗時(shí): %d ns%n", svEnd - svStart);
          }
      }
      

      四、ScopedValue高級(jí)特性

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

      ScopedValue確實(shí)提供了很多強(qiáng)大的功能。

      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() {
              // 可以訪問(wèn)ScopedValue,無(wú)需參數(shù)傳遞
              UserContext user = USER_CONTEXT.get();
              RequestInfo request = REQUEST_INFO.get();
              
              return "用戶數(shù)據(jù): " + user.getUserId() + ", 請(qǐng)求: " + 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)層作用域可以訪問(wèn)外層值
                  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ù)原來(lái)的父值
                      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()) {
                      
                      // 虛擬線程自動(dòng)繼承ScopedValue
                      for (int i = 0; i < 3; i++) {
                          final int taskId = i;
                          scope.fork(() -> {
                              // 可以訪問(wèn)父線程的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 錯(cuò)誤處理和調(diào)試

      /**
       * ScopedValue錯(cuò)誤處理和調(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已自動(dòng)清理,無(wú)需額外處理
              }
              
              // 驗(yàn)證值已清理
              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", () -> {
                  // 注冊(cè)清理鉤子
                  Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                      cleanupLog.add("資源清理完成");
                  }));
                  
                  // 即使這里發(fā)生異常,ScopedValue也會(huì)清理
                  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)試場(chǎng)景
              ScopedValue.runWhere(COUNT_VALUE, COUNT_VALUE.get() + 1, () -> {
                  System.out.println("嵌套調(diào)試 - COUNT_VALUE: " + COUNT_VALUE.get());
              });
          }
      }
      

      五、實(shí)戰(zhàn)案例

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

      讓我們用一個(gè)Web應(yīng)用中的用戶上下文管理來(lái)展示ScopedValue在真實(shí)項(xiàng)目中的應(yīng)用。

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

      image

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

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

      image

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

      /**
       * Web應(yīng)用ScopedValue定義
       */
      public class WebScopedValues {
          
          // 用戶上下文
          public static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
          
          // 請(qǐng)求信息
          public static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
          
          // 數(shù)據(jù)庫(kù)連接(可選)
          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;
          
          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);
          }
      }
      
      /**
       * 請(qǐng)求信息
       */
      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 過(guò)濾器實(shí)現(xiàn)

      /**
       * 認(rèn)證過(guò)濾器 - 使用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;
              
              // 生成請(qǐng)求ID
              String requestId = generateRequestId();
              
              // 提取請(qǐng)求信息
              RequestInfo requestInfo = extractRequestInfo(httpRequest, requestId);
              
              // 認(rèn)證用戶
              UserContext userContext = authenticateUser(httpRequest);
              
              // 在作用域內(nèi)執(zhí)行請(qǐng)求處理
              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("請(qǐng)求處理異常", e);
                          throw new RuntimeException("過(guò)濾器異常", e);
                      }
                  }
              );
              
              // 作用域結(jié)束后自動(dòng)清理所有ScopedValue
              log.info("請(qǐng)求處理完成: {}", 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());
              
              // 驗(yàn)證權(quán)限
              if (!userContext.getUserId().equals(request.getUserId())) {
                  throw new PermissionDeniedException("無(wú)權(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ù),無(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,無(wú)需參數(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() {
              // 無(wú)需傳遞用戶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獲取請(qǐng)求信息用于日志
              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);
          }
      }
      
      /**
       * 錯(cuò)誤響應(yīng)
       */
      @Data
      @AllArgsConstructor
      class ErrorResponse {
          private String error;
          private String message;
          private String traceId;
          private long timestamp = System.currentTimeMillis();
      }
      

      六、遷移指南:從ThreadLocal到ScopedValue

      有些小伙伴可能擔(dān)心遷移成本,其實(shí)從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 {
                  // 必須手動(dòng)清理
                  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();
                      }
                  );
                  
                  // 自動(dòng)清理,無(wú)需finally塊
              } catch (Exception e) {
                  // 異常處理
              }
          }
          
          /**
           * 業(yè)務(wù)處理 - 舊方式
           */
          private void processBusinessOld() {
              // 需要處理null值
              UserContext user = TL_USER.get();
              if (user == null) {
                  throw new IllegalStateException("用戶上下文未設(shè)置");
              }
              
              Connection conn = TL_CONN.get();
              if (conn == null) {
                  throw new IllegalStateException("數(shù)據(jù)庫(kù)連接未設(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ù)雜遷移場(chǎng)景:嵌套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)層值
                  });
                  // 自動(dòng)恢復(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<>();
          
          /**
           * 橋接模式:同時(shí)支持兩種方式
           */
          public void bridgePattern() {
              UserContext user = new UserContext("bridge_user");
              
              // 在新作用域內(nèi)執(zhí)行
              ScopedValue.runWhere(SV_USER, user, () -> {
                  // 同時(shí)設(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) {
                  // 對(duì)于set操作,需要在適當(dāng)?shù)淖饔糜蛘{(diào)用
                  throw new UnsupportedOperationException("請(qǐng)使用ScopedValue.runWhere");
              }
              
              public UserContext get() {
                  try {
                      return scopedValue.get();
                  } catch (Exception e) {
                      // 如果不在作用域內(nèi),返回null(模擬ThreadLocal行為)
                      return null;
                  }
              }
              
              public void remove() {
                  // 無(wú)需操作,ScopedValue自動(dòng)管理
              }
          }
          
          /**
           * 混合業(yè)務(wù)處理
           */
          private void processMixedBusiness() {
              // 新代碼使用ScopedValue
              UserContext svUser = SV_USER.get();
              System.out.println("ScopedValue用戶: " + svUser.getUserId());
              
              // 舊代碼使用ThreadLocal(通過(guò)橋接設(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)過(guò)上面的深度剖析,我們來(lái)總結(jié)一下ScopedValue的核心優(yōu)勢(shì)。

      核心優(yōu)勢(shì)

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

      遷移決策指南

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

      ThreadLocal vs ScopedValue 選擇口訣

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

      技術(shù)對(duì)比

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

      最后的建議

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

      1. 學(xué)習(xí)掌握:盡快學(xué)習(xí)掌握ScopedValue的使用
      2. 新項(xiàng)目首選:在新項(xiàng)目中優(yōu)先使用ScopedValue
      3. 逐步遷移:在老項(xiàng)目中制定合理的遷移計(jì)劃
      4. 關(guān)注生態(tài):關(guān)注相關(guān)框架對(duì)ScopedValue的支持

      最后說(shuō)一句(求關(guān)注,別白嫖我)

      如果這篇文章對(duì)您有所幫助,或者有所啟發(fā)的話,幫忙關(guān)注一下我的同名公眾號(hào):蘇三說(shuō)技術(shù),您的支持是我堅(jiān)持寫作最大的動(dòng)力。

      求一鍵三連:點(diǎn)贊、轉(zhuǎn)發(fā)、在看。

      關(guān)注公眾號(hào):【蘇三說(shuō)技術(shù)】,在公眾號(hào)中回復(fù):進(jìn)大廠,可以免費(fèi)獲取我最近整理的10萬(wàn)字的面試寶典,好多小伙伴靠這個(gè)寶典拿到了多家大廠的offer。

      更多項(xiàng)目實(shí)戰(zhàn)在我的技術(shù)網(wǎng)站:http://www.susan.net.cn/project

      posted @ 2025-11-01 17:32  蘇三說(shuō)技術(shù)  閱讀(505)  評(píng)論(2)    收藏  舉報(bào)
      主站蜘蛛池模板: 色噜噜狠狠色综合成人网| 亚洲码与欧洲码区别入口| 视频一区二区三区高清在线| 亚洲中文字幕无码av永久| 国产成人精品无码专区| 亚洲国产一区二区三区四| 亚洲另类欧美综合久久图片区| 国产精品99久久不卡| 性色欲情网站iwww九文堂| 久久无码人妻精品一区二区三区| 成人拍拍拍无遮挡免费视频| 美女扒开尿口让男人桶| 亚洲精品www久久久久久| 91精品人妻中文字幕色| 精品国产一区二区三区性色| 色欲综合久久中文字幕网| 国产成人精品日本亚洲| 成人精品视频一区二区三区| 粉嫩jk制服美女啪啪| 国产精品综合色区av| 国内不卡一区二区三区| 钦州市| 成人福利一区二区视频在线| 日本一区二区三区小视频| 免费无码VA一区二区三区| 成人无码潮喷在线观看| 亚欧洲乱码视频一二三区| 亚洲日韩国产中文其他| 亚洲精品成人无限看| 无码伊人久久大杳蕉中文无码| 久久精品道一区二区三区| 成 人 色 网 站免费观看| 日本熟妇XXXX潮喷视频| 国产黄色精品一区二区三区 | 中文国产成人精品久久一| 国产热A欧美热A在线视频| 69精品无人区国产一区| 高清色本在线www| 人妻精品久久无码区 | 亚洲 中文 欧美 日韩 在线| 极品无码国模国产在线观看|