> ## Documentation Index
> Fetch the complete documentation index at: https://blog.pig4cloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ScopedValues

## 告别 ThreadLocal：拥抱 Java 新一代上下文管理利器 ScopedValue

在 Java 并发编程的漫长历史中，`ThreadLocal` 一直是开发者们在处理线程封闭数据、传递上下文时的首选方案。然而，随着 Project Loom 和虚拟线程的到来，`ThreadLocal` 的一些固有缺陷也愈发凸
显。为了更好地适应现代并发模型，Java 带来了全新的解决方案——`ScopedValue`。本文将深入探讨 `ThreadLocal` 的痛点，并详细介绍 `ScopedValue` 如何以其优雅的设计，成为更安全、更高效的替代方案。

### `ThreadLocal` 的困境：看似美好，实则暗藏风险

`ThreadLocal` 的核心思想是为每个线程提供一个独立的变量副本，从而实现在多线程环境下的数据隔离。这在很多场景下非常有用，例如，在 Web 应用中保存每个用户的身份信息、在复杂的调用链中传递事务 ID 等。

但这种便利性背后，也隐藏着一些不容忽视的问题：

* **内存泄漏风险**：`ThreadLocal` 的生命周期与线程绑定。如果在使用线程池的场景下，线程被复用，而 `ThreadLocal` 变量没有被显式地通过 `remove()` 方法清理，那么这个变量的实例将会一直存在于线程中，导致内存泄漏。在高并发服务中，这可能成为一个致命的隐患。
* **父子线程继承问题**：默认情况下，`ThreadLocal` 的值不会从父线程传递给子线程。虽然可以通过 `InheritableThreadLocal` 来解决，但这增加了复杂性，并且在虚拟线程模型下，这种继承机制的开销和行为变得更加不可预测。
* **数据可变性带来的不确定性**：`ThreadLocal` 中存储的对象是可变的。这意味着在线程的任何执行点，你都可以修改这个值。这种不受限制的修改能力破坏了数据的封装性，使得在复杂的业务逻辑中追踪数据状态变得异常困难，容易引发难以排查的 Bug。

### `ScopedValue` 登场：为现代并发而生的新星

`ScopedValue` 是在 Java 20 中作为预览功能引入，并在后续版本中不断完善的。它的设计目标就是为了解决 `ThreadLocal` 的上述痛点，特别是在虚拟线程和结构化并发大行其道的今天。

与 `ThreadLocal` 不同，`ScopedValue` 的核心理念是**在有限的作用域（Scope）内共享不可变的数据**。

#### `ScopedValue` 的核心特性

1. **不可变性（Immutability）**：一旦一个 `ScopedValue` 在某个作用域内被绑定了值，那么在这个作用域的整个生命周期内，它的值都是不可变的。这从根本上杜绝了数据在调用链中被意外篡改的风险，让代码逻辑更加清晰和安全。

2. **作用域限制（Scoped Lifetime）**：`ScopedValue` 的值只在特定的代码块（作用域）内有效。一旦代码执行离开这个作用域，`ScopedValue` 的值就会被自动销毁，无需任何手动清理。这彻底解决了 `ThreadLocal` 的内存泄漏问题。

3. **高效的父子线程数据传递**：`ScopedValue` 被设计为可以高效地在线程间（尤其是父子线程和虚拟线程）共享。当在一个作用域内创建新的线程（无论是平台线程还是虚拟线程）时，`ScopedValue` 的值会自动、廉价地传递过去，完美契合了结构化并发的需求。

### `ScopedValue` vs. `ThreadLocal`：一场范式的革新

| 特性       | `ThreadLocal`                     | `ScopedValue`            |
| -------- | --------------------------------- | ------------------------ |
| **可变性**  | 可变 (Mutable)                      | 不可变 (Immutable)          |
| **生命周期** | 与线程绑定                             | 与代码作用域绑定                 |
| **清理机制** | 需要手动调用 `remove()`                 | 作用域结束时自动清理               |
| **线程继承** | 默认不继承，需用 `InheritableThreadLocal` | 自动、高效地在作用域内继承            |
| **适用场景** | 传统的、需要线程独立可变状态的场景                 | 虚拟线程、结构化并发、需要传递不可变上下文的场景 |

### `ScopedValue` 实战：代码示例

让我们通过一个简单的例子，直观地感受一下 `ScopedValue` 的用法。

```java theme={null}
// 1. 定义一个 ScopedValue
private static final ScopedValue<String> LOGGED_IN_USER = ScopedValue.newInstance();

public void handleRequest(String user) {
    // 2. 使用 where 方法定义作用域并绑定值
    ScopedValue.where(LOGGED_IN_USER, user)
               .run(() -> {
                   // 3. 在作用域内，任何地方都可以安全地获取到值
                   System.out.println("User in controller: " + LOGGED_IN_USER.get());
                   callBusinessLogic();
               });
}

public void callBusinessLogic() {
    // 即使在另一个方法中，只要仍在作用域内，就能获取
    System.out.println("User in business logic: " + LOGGED_IN_USER.get());

    // 尝试在作用域内修改值会导致异常
    // LOGGED_IN_USER.set("new-user"); // This would not be possible
}

// 示例调用
handleRequest("admin");
```

在上面的代码中，`LOGGED_IN_USER` 的值 "admin" 只在 `run()` 方法所代表的作用域内有效。当 `run()` 方法执行完毕，这个绑定关系就自动解除了。你不需要担心任何清理工作，也不用害怕 `callBusinessLogic` 方法会意外修改这个值。

### 总结：为什么你应该选择 ScopedValue？

`ScopedValue` 的出现，不仅仅是对 `ThreadLocal` 的一次简单升级，更是一次对 Java 并发上下文中数据管理的范式革新。它以其**不可变性**、**作用域生命周期**和**为虚拟线程优化**的特性，为我们带来了前所未有的安全性和编码便利性。

虽然 `ThreadLocal` 在一些遗留系统和特定场景中仍有其用武之地，但对于所有新的 Java 应用，尤其是在你计划拥抱虚拟线程和结构化并发的未来时，`ScopedValue` 无疑是那个更优、更安全、更现代的选择。是时候告别 `ThreadLocal` 的那些历史包袱，开始在你的项目中享受 `ScopedValue` 带来的清爽和稳健了。
