实战指南:理解 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结构关系:

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

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

  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.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()的时候,都会进行清理:

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(),源码如下:

public T get() {}

public void set(T value){}

public void remove(){}

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

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

ThreadLocal.set()方法源码详解

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()方法

基本流程可总结如下:

ThreadLocalMap.get()方法详解

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

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

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

ThreadLocal.remove()方法源码详解

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

static class ThreadLocalMap {

    // 内部 Entry 类,继承自 WeakReference<ThreadLocal<?>>
    static class Entry extends WeakReference<ThreadLocal<?>> {
        // 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 的基本使用示例:

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<Integer> 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实现的上下文管理组件ContextManager

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

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

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

(一)定义 ContextManager

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

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<ContextManager> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();

    // 实例变量,维护每个上下文中所有的状态数据
    private final ConcurrentMap<String, Object> 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 类来管理用户的上下文信息。

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、商品信息和购买数量,并执行了结账操作。

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

(三)扩展 ContextManager 的使用方式

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

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 <X extends Throwable> void runWithNewContext(Runnable task) throws X {
        beginContext();
        try {
            task.run();
        } finally {
            endContext();
        }
    }

    // 在新的上下文中执行任务,并返回结果
    public static <T, X extends Throwable> T supplyWithNewContext(Supplier<T> 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,用于在指定的上下文中执行任务:

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 <X extends Throwable> void runWithExistingContext(ContextManager context, Runnable task) throws X {
        supplyWithExistingContext(context, () -> {
            task.run();
            return null;
        });
    }

    public static <T, X extends Throwable> T supplyWithExistingContext(ContextManager context, Supplier<T> 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,确保任务在执行时可以正确传递和恢复上下文信息:

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<Runnable> 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 是否正确传递和恢复上下文:

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 实现一个上下文管理组件(附源码)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/707086.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【StableDiffusion】Embedding 底层原理,Prompt Embedding,嵌入向量

Embedding 是什么&#xff1f; Embedding 是将自然语言词汇&#xff0c;映射为 固定长度 的词向量 的技术 说到这里&#xff0c;需要介绍一下 One-Hot 编码 是什么。 One-Hot 编码 使用了众多 5000 长度的1维矩阵&#xff0c;每个矩阵代表一个词语。 这有坏处&#xff0c…

vscode卡顿问题处理(vue-official插件)

vue官方扩展由volar升级为vue-official&#xff0c;部分人的ide会变得非常卡顿&#xff0c;这是由于vscode本身一些问题导致&#xff0c;如下图作者解释&#xff1a; 解决方式&#xff1a; 通过禁用Hybrid模式&#xff0c;不使用tsserver来接管语言支持&#xff0c;卡顿会缓解…

进击算法工程师深度学习课程

"进击算法工程师深度学习课程"旨在培养学员在深度学习领域的专业技能和实战经验。课程涵盖深度学习基础理论、神经网络架构、模型优化方法等内容&#xff0c;通过项目实践和算法实现&#xff0c;帮助学员掌握深度学习算法原理和应用&#xff0c;提升在算法工程师领域…

如何用多媒体沙盘实现智能交互体验?

随着多媒体技术在内容展示领域的迅猛进步&#xff0c;智能化信息交互方式已然跃升为公众瞩目的焦点&#xff0c;而展厅作为信息传递与产品展示的核心阵地&#xff0c;正面临着提升交互体验、强化信息传递效果的迫切需求。因此&#xff0c;以多媒体沙盘、LED屏幕等创新装置为媒介…

94. 二叉树的中序遍历(Swift实现, 迭代)

题目描述 使用迭代方法解题 class TreeNode {var val: Intvar left: TreeNode?var right: TreeNode?init(_ val: Int) {self.val valself.left nilself.right nil} }func inorderTraversal(_ root: TreeNode?) -> [Int] {var result [Int]() // 用于存储中序遍历…

[深度学习]基于C++和onnxruntime部署yolov10的onnx模型

基于C和ONNX Runtime部署YOLOv10的ONNX模型&#xff0c;可以遵循以下步骤&#xff1a; 准备环境&#xff1a;首先&#xff0c;确保已经下载后指定版本opencv和onnruntime的C库。 模型转换&#xff1a;按照官方源码&#xff1a;https://github.com/THU-MIG/yolov10 安装好yolov…

【OpenVINO™】使用 OpenVINO™ C++ 异步推理接口部署YOLOv8 ——在Intel IGPU 上实现80+FPS视频推理

​ OpenVINO Runtime支持同步或异步模式下的推理。Async API的主要优点是&#xff0c;当设备忙于推理时&#xff0c;应用程序可以并行执行其他任务&#xff08;例如&#xff0c;填充输入或调度其他请求&#xff09;&#xff0c;而不是等待当前推理首先完成。 当我们使用异步API…

图片查看器

目录 一 原型 二 源码 一 原型 二 源码 namespace 图片查看器 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){//默认显示第一张图片pictureBox1.Image imageList1.Images[0];}private v…

13. 第十三章 案例研究-选择数据结构

13. 案例研究-选择数据结构 到这里尼应该已经学会了Python的核心数据结构, 也见过了一些使用它们的算法. 如果你想要更多地了解算个发可以阅读第21章. 本章配合联系介绍一个案例分析, 帮你思考如何选择数据结构并如何使用它们.13.1 单词频率分析 1. 练习1 编写一个程序, 读入…

《Brave New Words 》9.1 AI 世界中的就业

Part IX: Work and What Comes Next 第九部分&#xff1a;工作及其未来发展 The one who plants trees, knowing that he will never sit in their shade, has at least started to understand the meaning of life. —Rabindranath Tagore 种树的人&#xff0c;虽然知道他永远…

上位机图像处理和嵌入式模块部署(h750 mcu串口命令处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面学习103和407的时候&#xff0c;当时学过串口的收发。不过当时使用的主要是阻塞的方式。这一次&#xff0c;我们看下应该怎么利用中断的形式进…

基于flask的网站如何使用https加密通信

文章目录 内容简介网站目录示例生成SSL证书单独使用Flask使用WSGI服务器Nginx反向代理参考资料 内容简介 HTTPS 是一种至关重要的网络安全协议&#xff0c;它通过在 HTTP 协议之上添加 SSL/TLS 层来确保数据传输的安全性和完整性。这有助于防止数据在客户端和服务器之间传输时…

前端实现获取后端返回的文件流并下载

前端实现获取后端返回的文件流并下载 方法一&#xff1a;使用Axios实现文件流下载优点缺点 方法二&#xff1a;使用封装的Request工具实现文件流下载优点缺点 方法三&#xff1a;直接通过URL跳转下载优点缺点 结论 在前端开发中&#xff0c;有时需要从后端获取文件流&#xff0…

Android studio如何导入项目

打开解压好的安装包 找到build.gradle文件 打开查看gradle版本 下载对应的gradle版本Index of /gradle/&#xff08;镜像网站&#xff09; 下载all的对应压缩包 配置gradle的环境变量 新建GRADLE_HOME 将GRADLE_HOME加入到path中 将项目在Android studio中打开进行配置 将gr…

前端 CSS 经典:在 Vue3 中使用渐进式图片

1. 什么是渐进式图片 当我们网站会加载很多图片的时候&#xff0c;有些图片尺寸很大&#xff0c;加载就会很慢&#xff0c;会导致页面长时间陷入白屏状态&#xff0c;用户体验很不好。所以可以使用渐进式图片&#xff0c;先给用户展示模糊图&#xff0c;这些图尺寸小&#xff…

嵌入式硬件VS软件,到底哪个更难?

在嵌入式系统开发中&#xff0c;硬件和软件是密不可分的两个方面。但是&#xff0c;究竟是硬件开发更具挑战性&#xff0c;还是软件开发更难以应对呢&#xff1f;本文将就这一问题展开讨论&#xff0c;探究嵌入式硬件和软件在开发过程中的各种挑战与特点。 一、硬件开发&#…

5.7 Python内置函数

文章目录 1. 内置模块Aabs()all()any()ascii() Bbin()bool()bytearra()bytes() Ccallable()chr()classmethod()compile()complex() Ddelattr()dict()dir()divmod() Eenumerate()eval()exec()execfile() Ffile()filter()float()format()frozenset() Ggetattr()globals() Hhasatt…

C++ 23 之 构造函数和析构函数

c23构造函数和析构函数.cpp #include <iostream> #include <string> using namespace std;class Person2{ public:// 构造函数 没有返回值&#xff0c;不能写void;函数名和类名一致&#xff1b;可以设置参数&#xff0c;可以函数重载&#xff1b;系统自动调用&…

人工智能将成为数学家的“副驾驶”

人工智能将成为数学家的“副驾驶” 数学传统上是一门独立的科学。1986年&#xff0c;安德鲁怀尔斯为了证明费马定理&#xff0c;退到书房里呆了7年。由此产生的证明往往很难让同事们理解&#xff0c;有些至今仍有争议。但近年来&#xff0c;越来越多的数学领域被严格地分解为各…

[大模型]Phi-3-mini-4k-instruct langchain 接入

环境准备 在 autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 。 接下来打开刚刚租用服务器的 JupyterLab&#xff0c;并且打开其中的终端开始环境配置、模型下载和运行演示。 创建工作…