实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

目录

一、ThreadLocal基本知识回顾分析

(一)ThreadLocal原理

(二)既然ThreadLocalMap的key是弱引用,GC之后key是否为null?

(三)ThreadLocal中的内存泄漏问题及JDK处理方法

(四)部分核心源码回顾

ThreadLocal.set()方法源码详解

ThreadLocalMap.get()方法详解

ThreadLocal.remove()方法源码详解

(五)简单的直观体会

二、基于Threadlocal实现的上下文管理组件ContextManager

(一)定义 ContextManager 类

(二)使用 ContextManager 进行上下文管理

(三)扩展 ContextManager 的使用方式

三、在线程池中传递ContextManager

(一)增加静态方法,用于在已有的上下文中执行任务

(二)自定义线程池实现

(三)测试自定义线程池

四、总结


干货分享,感谢您的阅读!

探讨如何基于 ThreadLocal 实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。通过具体的代码示例和实战展示 ThreadLocal 如何为多线程编程提供一种简洁而高效的上下文管理方案。

一、ThreadLocal基本知识回顾分析

(一)ThreadLocal原理

ThreadLocal 是 Java 提供的一个用于线程级别数据存储的类。它为每个线程提供了独立的变量副本,使得每个线程都能独立地操作自己的变量,而不会与其他线程的变量冲突。这种机制特别适用于需要线程隔离的场景,通过 ThreadLocal,我们可以确保同一个变量在不同线程中拥有各自独立的值。

我们先来看下Thread、ThreadLocalMap、ThreadLocal结构关系:

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

  • 每个Thread都有一个ThreadLocalMap变量
  • ThreadLocalMap内部定义了Entry(ThreadLocal k, Object v)节点类,这个节点继承了WeakReference类泛型为ThreacLocal

ThreadLocal主要作用就是实现线程间变量隔离,对于一个变量,每个线程维护一个自己的实例,防止多线程环境下的资源竞争,那ThreadLocal是如何实现这一特性的呢?基本原理实现如下:

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

  1. 每个Thread对象中都包含一个ThreadLocal.ThreadLocalMap类型的threadlocals成员变量;

  2. 该map对应的每个元素Entry对象中:key是ThreadLocal对象的弱引用,value是该threadlocal变量在当前线程中的对应的变量实体;

  3. 当某一线程执行获取该ThreadLocal对象对应的变量时,首先从当前线程对象中获取对应的threadlocals哈希表,再以该ThreadLocal对象为key查询哈希表中对应的value;

  4. 由于每个线程独占一个threadlocals哈希表,因此线程间ThreadLocal对象对应的变量实体也是独占的,不存在竞争问题,也就避免了多线程问题。

(二)既然ThreadLocalMapkey是弱引用,GC之后key是否为null

在搞清楚这个问题之前,我们需要先搞清楚Java的四种引用类型

  • 强引用:new出来的对象就是强引用,只要强引用存在,垃圾回收器就永远不会回收被引用的对象,哪怕内存不足的时候。
  • 软引用:使用SoftReference修饰的对象被称为软引用,在内存要溢出的时候软引用指向的对象会被回收。
  • 弱引用:使用WeakReference修饰的对象被称为弱引用,只要发生垃圾回收,被弱引用指向的对象就会被回收。
  • 虚引用:虚引用是最弱的引用,用PhantomReference进行定。唯一的作用就是用来队列接受对象即将死亡的通知。

这个问题的答案是不为null,从上图的图示就可以直接看出。

(三)ThreadLocal中的内存泄漏问题及JDK处理方法

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

由图可知,ThreadLocal.ThreadLocalMap 对应的Entry中,key为ThreadLocal对象的弱引用,方法执行对应栈帧中的ThreadLocal引用为强引用。当方法执行过程中,由于栈帧销毁或者主动释放等原因,释放了ThreadLocal对象的强引用,即表示该ThreadLocal对象可以被回收了。又因为Entry中key为ThreadLocal对象的弱引用,所以当jvm执行GC操作时是能够回收该ThreadLocal对象的。

Entry中value对应的是变量实体对象的强引用,因此释放一个ThreadLocal对象,是无法释放ThreadLocal.ThreadLocalMap中对应的value对象的,也就造成了内存泄漏。除非释放当前线程对象,这样整个threadlocals都被回收了。但是日常开发中会经常使用线程池等线程池化技术,释放线程对象的条件往往无法达到。

JDK处理的方法是,在ThreadLocalMap进行set()get()remove()的时候,都会进行清理:

```java
private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal k = e.get();
        if (k == key)
            return e;
        if (k == null)
            //如果key为null,对应的threadlocal对象已经被回收,清理该Entry
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
```

(四)部分核心源码回顾

ThreadLocalAPI很少就包含了4个,分别是get()set()remove()withInitial(),源码如下:

```java
public T get() {}

public void set(T value){}

public void remove(){}

public static  ThreadLocal withInitial(Supplier supplier) {

}
```
  • get() :从当前线程的 ThreadLocalMap 获取与当前 ThreadLocal 对象对应的值。如果 ThreadLocalMap 中不存在该值,则调用 setInitialValue() 方法进行初始化。
  • set(T value) :将当前线程的 ThreadLocalMap 中的值设置为给定的 value。如果当前线程没有 ThreadLocalMap,则会创建一个新的 ThreadLocalMap 并将值设置进去。
  • remove() :从当前线程的 ThreadLocalMap 中移除与当前 ThreadLocal 对象对应的值,帮助防止内存泄漏。
  • withInitial(Supplier supplier):返回一个新的 ThreadLocal 对象,其初始值由 Supplier 提供。这允许使用者在创建 ThreadLocal 时指定初始值。

针对这几个源码我们重点进行分析和体会。

ThreadLocal.set()方法源码详解

```java
pubic void set(T value) {
    // 获取当前线程
    Thread t = Threac.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 如果map不为null, 调用ThreadLocalMap.set()方法设置值
    if (map != null)
        map.set(this, value);
    else 
        // map为null,调用createMap()方法初始化创建map
        createMap(t, value);
}

// 返回线程的ThreadLocalMap.threadLocals
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 调用ThreadLocalMap构造方法创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap构造方法,传入firstKey, firstValue
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    // 初始化Entry表的容量 = 16
    table = new Entry[INITIAL_CAPACITY];
    // 获取ThreadLocal的hashCode值与运算得到数组下标
    int i = firsetKey.threadLocalHashCode & (INITAL_CAPACITY - 1);
    // 通过下标Entry表赋值
    table[i] = new Entry(firstKey, firstValue);
    // Entry表存储元素数量初始化为1
    size = 1;
    // 设置Entry表扩容阙值 默认为 len * 2 / 3
    setThreshold(INITIAL_CAPACITY);
}

private void setThreshold(int len) {
    threshold = len * 2 / 3
}
```

ThreadLocal.set()方法还是很简单的,核心方法在ThreadLocalMap.set()方法

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

基本流程可总结如下:

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

ThreadLocalMap.get()方法详解

```java
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 未找到的话,则调用setInitialValue()方法设置null
    return setInitialValue();
}

private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // key相等直接返回
    if (e != null && e.get() == key)
        return e;
    else
        // key不相等调用getEntryAfterMiss()方法
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    // 迭代往后查找key相等的entry
    while (e != null) {
        ThreadLocal k = e.get();
        if (k == key)
            return e;
        // 遇到key=null的entry,先进行探测式清理工作
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
```

主要包含两种情况,一种是hash计算出下标,该下标对应的Entry.key和我们传入的key相等的情况,另外一种就是不相等的情况。

相等情况: 相等情况处理很简单,直接返回value,如下图,比如get(ThreadLocal1)计算下标为4,且4存在Entry,且key相等,则直接返回value = 11

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

不相等情况 :不相等情况,以get(ThreadLocal2)为例计算下标为4,且4存在Entry,但key相等,这个时候则为往后迭代寻找key相等的元素,如果寻找过程中发现了有key = null的元素则回进行探测式清理操作。如下图:

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

迭代到index=5的数据时,此时Entry.key=null,触发一次探测式数据回收操作,执行expungeStaleEntry()方法,执行完后,index 5、8的数据都会被回收,而index 6、7的数据都会前移,此时继续往后迭代,到index = 6的时候即找到了key值相等的Entry数据,如下图:

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

ThreadLocal.remove()方法源码详解

```java
public void remove() {
    // 获取当前线程的 ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // 如果当前线程有 ThreadLocalMap,则在 map 中移除当前 ThreadLocal 的值
        m.remove(this);
}

static class ThreadLocalMap {

    // 内部 Entry 类,继承自 WeakReference>
    static class Entry extends WeakReference> {
        // ThreadLocal 对应的值
        Object value;

        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }

    // 线程局部变量哈希表
    private Entry[] table;

    private void remove(ThreadLocal key) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算当前 ThreadLocal 的哈希值在数组中的索引位置
        int i = key.threadLocalHashCode & (len - 1);

        // 从hash获取的下标开始,寻找key相等的entry元素清除
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            if (e.get() == key) {
                e.clear();  // 清除键的引用
                expungeStaleEntry(i);  // 清除相应的值
                return;
            }
        }
    }

    // 用于计算下一个索引位置
    private int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    // 清除无效的 Entry
    private void expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;

        // 清除给定槽位的 Entry
        tab[staleSlot].value = null;
        tab[staleSlot] = null;

        // Rehash until we encounter null
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal k = e.get();
            if (k == null) {
                e.value = null;
                tab[i] = null;
            } else {
                int h = k.threadLocalHashCode & (len - 1);
                if (h != i) {
                    tab[i] = null;

                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
    }
}

```

ThreadLocal.remove()核心是调用ThreadLocalMap.remove()方法,流程如下:

  1. 通过hash计算下标。
  2. 从散列表该下标开始往后查key相等的元素,如果找到则做清除操作,引用置为nullGC的时候key就会置为null,然后执行探测式清理处理。

(五)简单的直观体会

以下是 ThreadLocal 的基本使用示例:

```java
package org.zyf.javabasic.thread.threadLocal;

/**
 * @program: zyfboot-javabasic
 * @description: ThreadLocal 的基本使用示例
 * @author: zhangyanfeng
 * @create: 2024-06-02 13:22
 **/
public class ThreadLocalExample {
    private static ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        Runnable task = () -> {
            int value = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + " initial value: " + value);
            threadLocal.set(value + 1);
            System.out.println(Thread.currentThread().getName() + " updated value: " + threadLocal.get());
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

```

直接结果查看可感受到其ThreadLocal主要作用就是实现线程间变量隔离,对于一个变量,每个线程维护一个自己的实例,防止多线程环境下的资源竞争。

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

二、基于Threadlocal实现的上下文管理组件ContextManager

在实际开发中,我们经常需要维护一些上下文信息,这样可以避免在方法调用过程中传递过多的参数。例如,当 Web 服务器收到一个请求时,需要解析当前登录状态的用户,并在后续的业务处理中使用这个用户名。如果只需要维护一个上下文数据,如用户名,可以通过方法传参的方式,将用户名作为参数传递给每个业务方法。然而,如果需要维护的上下文信息较多,这种方式就显得笨拙且难以维护。

一个更加优雅的解决方案是使用 ThreadLocal 来实现请求线程的上下文管理。这样,同一线程中的所有方法都可以通过 ThreadLocal 对象直接读取和修改上下文信息,而无需在方法间传递参数。当需要维护多个上下文状态时,可以使用多个 ThreadLocal 实例来存储不同的信息。虽然这种方式在某些情况下也能接受,但在使用线程池时,问题就变得复杂了。因为线程池中的线程会被多个请求重复使用,如何将 ThreadLocal 中的上下文信息从主线程传递到线程池中的工作线程成为一个难题。

基于上述考虑,我们介绍一种基于 ThreadLocal 实现的上下文管理组件 ContextManager,它能够简化上下文信息的管理,并解决线程池环境中的上下文传递问题。

(一)定义 ContextManager

首先,定义一个 ContextManager 类用于管理上下文信息。

```java
package org.zyf.javabasic.thread.threadLocal;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @program: zyfboot-javabasic
 * @description: 用于管理上下文信息
 * @author: zhangyanfeng
 * @create: 2024-06-02 13:48
 **/
public class ContextManager {
    // 静态变量,维护不同线程的上下文
    private static final ThreadLocal CONTEXT_THREAD_LOCAL = new ThreadLocal<>();

    // 实例变量,维护每个上下文中所有的状态数据
    private final ConcurrentMap values = new ConcurrentHashMap<>();

    // 获取当前线程的上下文
    public static ContextManager getCurrentContext() {
        return CONTEXT_THREAD_LOCAL.get();
    }

    // 在当前上下文设置一个状态数据
    public void set(String key, Object value) {
        if (value != null) {
            values.put(key, value);
        } else {
            values.remove(key);
        }
    }

    // 在当前上下文读取一个状态数据
    public Object get(String key) {
        return values.get(key);
    }

    // 开启一个新的上下文
    public static ContextManager beginContext() {
        ContextManager context = CONTEXT_THREAD_LOCAL.get();
        if (context != null) {
            throw new IllegalStateException("A context is already started in the current thread.");
        }
        context = new ContextManager();
        CONTEXT_THREAD_LOCAL.set(context);
        return context;
    }

    // 关闭当前上下文
    public static void endContext() {
        CONTEXT_THREAD_LOCAL.remove();
    }
}

```

(二)使用 ContextManager 进行上下文管理

假设我们有一个在线商城系统,用户在进行购物时需要进行身份认证,并且在用户进行购物操作时,需要记录用户的购物车信息。我们可以使用 ContextManager 类来管理用户的上下文信息。

```java
package org.zyf.javabasic.thread.threadLocal;

import org.zyf.javabasic.skills.reflection.dto.Product;

/**
 * @program: zyfboot-javabasic
 * @description: 用户在进行购物时需要进行身份认证,并且在用户进行购物操作时,需要记录用户的购物车信息。
 * @author: zhangyanfeng
 * @create: 2024-06-02 14:02
 **/
public class ShoppingCartService {
    public void addToCart(Product product, int quantity) {
        // 开启一个新的上下文
        ContextManager.beginContext();
        try {
            // 将用户ID和商品信息设置到当前上下文中
            ContextManager.getCurrentContext().set("userId", getCurrentUserId());
            ContextManager.getCurrentContext().set("product", product);
            ContextManager.getCurrentContext().set("quantity", quantity);

            // 执行添加到购物车的逻辑
            // 这里可以调用其他方法,或者执行其他操作
            System.out.println("Adding product to cart...");

            checkout();

        } finally {
            // 关闭当前上下文
            ContextManager.endContext();
        }
    }

    public void checkout() {
        // 从当前上下文中读取用户ID和购物车信息
        String userId = (String) ContextManager.getCurrentContext().get("userId");
        Product product = (Product) ContextManager.getCurrentContext().get("product");
        int quantity = (int) ContextManager.getCurrentContext().get("quantity");

        // 执行结账逻辑
        // 这里可以根据购物车信息进行结账操作
        System.out.println("Checking out...");
        System.out.println("User ID: " + userId);
        System.out.println("Product: " + product.getName());
        System.out.println("Quantity: " + quantity);
    }

    private String getCurrentUserId() {
        // 模拟获取当前用户ID的方法
        return "user123";
    }

    public static void main(String[] args) {
        ShoppingCartService shoppingCartService = new ShoppingCartService();
        Product product = new Product();
        product.setName("iPhone");
        product.setId(1000);

        shoppingCartService.addToCart(product, 1);
    }
}

```

在这个示例中,ShoppingCartService 类模拟了一个购物车服务。在 addToCart() 方法中,我们开启了一个新的上下文,并将当前用户ID、商品信息和购买数量设置到上下文中。在 checkout() 方法中,我们从当前上下文中读取了用户ID、商品信息和购买数量,并执行了结账操作。

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

通过使用 ContextManager 类,我们可以轻松地在购物车服务中管理用户的上下文信息,而无需手动传递参数。

(三)扩展 ContextManager 的使用方式

我们可以给 ContextManager 添加类似的静态方法,以简化代码的书写。当前请视业务情况进行应用和分析。

```java
package org.zyf.javabasic.thread.threadLocal;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
 * @program: zyfboot-javabasic
 * @description: 用于管理上下文信息
 * @author: zhangyanfeng
 * @create: 2024-06-02 13:48
 **/
public class ContextManager {
    // 其他省去

    // 执行带有新的上下文的任务
    public static  void runWithNewContext(Runnable task) throws X {
        beginContext();
        try {
            task.run();
        } finally {
            endContext();
        }
    }

    // 在新的上下文中执行任务,并返回结果
    public static  T supplyWithNewContext(Supplier supplier) throws X {
        beginContext();
        try {
            return supplier.get();
        } finally {
            endContext();
        }
    }
}

```

三、在线程池中传递ContextManager

我们通过 ThreadLocal 实现了一个自定义的上下文管理组件 ContextManager,并通过 ContextManager.set()ContextManager.get() 方法在同一个线程中读写上下文中的状态数据。

现在,我们需要扩展这个功能,使其在一个线程执行过程中开启了一个 ContextManager,随后使用线程池执行任务时,也能获取到当前 ContextManager 中的状态数据。这在如下场景中很常见:服务收到一个用户请求,通过 ContextManager 将登录态数据存储到当前线程的上下文中,随后使用线程池执行一些耗时操作,并希望线程池中的线程也能访问这些登录态数据。

由于线程池中的线程和请求线程不是同一个线程,按照目前的实现,线程池中的线程无法访问请求线程的上下文数据。

为了解决这个问题,我们可以在提交 Runnable 时,将当前的 ContextManager 引用存储在 Runnable 对象中。当线程池中的线程开始执行时,将 ContextManager 替换到执行线程的上下文中,执行完成后再恢复原来的上下文。

(一)增加静态方法,用于在已有的上下文中执行任务

首先,添加静态方法 runWithExistingContextsupplyWithExistingContext,用于在指定的上下文中执行任务:

```java
package org.zyf.javabasic.thread.threadLocal;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
 * @program: zyfboot-javabasic
 * @description: 用于管理上下文信息
 * @author: zhangyanfeng
 * @create: 2024-06-02 13:48
 **/
public class ContextManager {
    // 省略

    public static  void runWithExistingContext(ContextManager context, Runnable task) throws X {
        supplyWithExistingContext(context, () -> {
            task.run();
            return null;
        });
    }

    public static  T supplyWithExistingContext(ContextManager context, Supplier supplier) throws X {
        ContextManager oldContext = CONTEXT_THREAD_LOCAL.get();
        CONTEXT_THREAD_LOCAL.set(context);
        try {
            return supplier.get();
        } finally {
            if (oldContext != null) {
                CONTEXT_THREAD_LOCAL.set(oldContext);
            } else {
                CONTEXT_THREAD_LOCAL.remove();
            }
        }
    }

}

```

(二)自定义线程池实现

创建一个自定义线程池 ContextAwareThreadPoolExecutor,确保任务在执行时可以正确传递和恢复上下文信息:

```java
package org.zyf.javabasic.thread.threadLocal;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static org.zyf.javabasic.thread.threadLocal.ContextManager.runWithExistingContext;

/**
 * @program: zyfboot-javabasic
 * @description: 自定义线程池 ContextAwareThreadPoolExecutor
 * @author: zhangyanfeng
 * @create: 2024-06-02 20:23
 **/
public class ContextAwareThreadPoolExecutor extends ThreadPoolExecutor {

    public ContextAwareThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public static ContextAwareThreadPoolExecutor newFixedThreadPool(int nThreads) {
        return new ContextAwareThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    }

    @Override
    public void execute(Runnable command) {
        ContextManager context = ContextManager.getCurrentContext();
        super.execute(() -> runWithExistingContext(context, command::run));
    }
}

```

(三)测试自定义线程池

验证 ContextAwareThreadPoolExecutor 是否正确传递和恢复上下文:

```java
package org.zyf.javabasic.thread.threadLocal;

import org.junit.Test;

import java.util.concurrent.ExecutorService;

/**
 * @program: zyfboot-javabasic
 * @description: 验证 ContextAwareThreadPoolExecutor 是否正确传递和恢复上下文
 * @author: zhangyanfeng
 * @create: 2024-06-02 20:25
 **/
public class ContextManagerTest {
    @Test
    public void testContextAwareThreadPoolExecutor() {
        ContextManager.beginContext();
        try {
            ContextManager.getCurrentContext().set("key", "value out of thread pool");
            Runnable r = () -> {
                String value = (String) ContextManager.getCurrentContext().get("key");
                System.out.println("Value in thread pool: " + value);
            };

            ExecutorService executor = ContextAwareThreadPoolExecutor.newFixedThreadPool(10);
            executor.execute(r);
            executor.submit(r);
        } finally {
            ContextManager.endContext();
        }

        /** 执行结果
         * Value in thread pool: value out of thread pool
         * Value in thread pool: value out of thread pool
         */
    }

    @Test
    public void testContextAwareThreadPoolExecutorWithNewContext() {
        ContextManager.runWithNewContext(() -> {
            ContextManager.getCurrentContext().set("key", "value out of thread pool");
            Runnable r = () -> {
                String value = (String) ContextManager.getCurrentContext().get("key");
                System.out.println("Value in thread pool: " + value);
            };

            ExecutorService executor = ContextAwareThreadPoolExecutor.newFixedThreadPool(10);
            executor.execute(r);
            executor.submit(r);
        });

        /** 执行结果
         * Value in thread pool: value out of thread pool
         * Value in thread pool: value out of thread pool
         */
    }
}

```

验证ContextAwareThreadPoolExecutor 是否能正确传递和恢复上下文信息。测试用例涵盖了两种情况:

  1. 在当前上下文中执行任务,并使用自定义线程池执行任务。
  2. 在新的上下文中执行任务,并使用自定义线程池执行任务。

这两种情况覆盖了在不同上下文环境中使用线程池的情况,确保了上下文信息能够正确传递和恢复。因此,验证内容是完备的,没有问题。

四、总结

探讨如何基于 ThreadLocal 实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。通过具体的代码示例和实战展示 ThreadLocal 如何为多线程编程提供一种简洁而高效的上下文管理方案。

参考文章

https://www.cnblogs.com/wupeixuan/p/12638203.html

一张图看懂Java中的ThreadLocal原理_threadlocal原理图解-CSDN博客

ThreadLocal原理 · 进击的java菜鸟

一文搞懂ThreadLocal原理-51CTO.COM

滑动验证页面

基于 ThreadLocal 实现一个上下文管理组件(附源码)

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/6700.html

(0)
LomuLomu
上一篇 2025 年 1 月 17 日 下午9:03
下一篇 2025 年 1 月 17 日 下午9:33

相关推荐

  • 已解决:java.lang.IllegalStateException 异常的正确解决方法,亲测有效!!!

    在Java开发中,java.lang.IllegalStateException 是一个常见的运行时异常,通常表示程序的状态与预期不符。在一些情况下,方法调用的前置条件没有得到满足,或者对象的状态不适合执行某个操作时,会抛出此异常。本文将详细分析IllegalStateException 的成因,并提供多种有效的解决方案,帮助开发者避免或解决这些问题。 1.…

    未分类 2025 年 1 月 12 日
    49900
  • 深入解析 Spring AI 系列:以OpenAI与Moonshot案例为例寻找共同点

    今天,我们将重点探讨对接的业务逻辑。为了帮助大家更直观地掌握其中的规律性,我将通过对比OpenAI与《月之暗面》中的Moonshot两个案例来阐述这一点。通过这样的对比,大家可以更清晰地看到,这些对接业务的整体框架其实非常相似。换句话说,我们要做的工作只是其中的一小部分,但它同样是关键的一环。 好了,接下来我们就开始深入了解这个话题。 模型对接 我们首先需要…

    2025 年 1 月 11 日
    47300
  • Java刷题常见的集合类,各种函数的使用以及常见的类型转化等等

    目录 前言 集合类 ArrayList 1. 创建和初始化 ArrayList 2.添加元素 add 3.获取元素 get 4.删除元素 remove 5.检查元素 6.遍历 ArrayList LinkedList Stack 1. 创建Stack对象 2. 压入元素 (push) 3. 弹出元素 (pop) 4. 查看栈顶元素 (peek) 5. 检查栈…

    2025 年 1 月 6 日
    38400
  • Django 3 Web应用开发实战PDF、EPUB免费下载

    适读人群 :适合有一定Python基础的Web开发人员阅读,也可用作培训机构和大中专院校相关专业的教学参考书。 以DjangoWeb项目开发为主线,从源码的角度,深入剖析Django3企业级开发技术。 电子版仅供预览,下载后24小时内务必删除,支持正版,喜欢的请购买正版书籍 点击原文去下载 书籍信息 作者: 黄永祥出版社: 清华大学出版社出版年: 2021-…

    2025 年 1 月 10 日
    48800
  • 华为OD机试E卷 –考勤信息–24年OD统一考试(Java & JS & Python & C & C++)

    文章目录 题目描述 输入描述 输出描述 用例 题目解析 JS算法源码 Java算法源码 python算法源码 c算法源码 c++算法源码 题目描述 公司用一个字符串来表示员工的出勤信息• absent:缺勤• late:迟到• leaveearly:早退• present:正常上班现需根据员工出勤信息,判断本次是否能获得出勤奖,能获得出勤奖的条件如下:• 缺…

    未分类 2024 年 12 月 28 日
    62800

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信