赞
踩
目录
(二)既然ThreadLocalMap的key是弱引用,GC之后key是否为null?
(三)ThreadLocal中的内存泄漏问题及JDK处理方法
二、基于Threadlocal实现的上下文管理组件ContextManager
干货分享,感谢您的阅读!
探讨如何基于 ThreadLocal
实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。通过具体的代码示例和实战展示 ThreadLocal
如何为多线程编程提供一种简洁而高效的上下文管理方案。
ThreadLocal基本知识回顾分析
ThreadLocal原理
ThreadLocal
是 Java 提供的一个用于线程级别数据存储的类。它为每个线程提供了独立的变量副本,使得每个线程都能独立地操作自己的变量,而不会与其他线程的变量冲突。这种机制特别适用于需要线程隔离的场景,通过 ThreadLocal
,我们可以确保同一个变量在不同线程中拥有各自独立的值。
我们先来看下Thread、ThreadLocalMap、ThreadLocal结构关系:
Thread
都有一个ThreadLocalMap
变量ThreadLocalMap
内部定义了Entry(ThreadLocal<?> k, Object v)节点类,这个节点继承了WeakReference
类泛型为ThreacLocal
类ThreadLocal
主要作用就是实现线程间变量隔离,对于一个变量,每个线程维护一个自己的实例,防止多线程环境下的资源竞争,那ThreadLocal
是如何实现这一特性的呢?基本原理实现如下:
每个Thread
对象中都包含一个ThreadLocal.ThreadLocalMap
类型的threadlocals
成员变量;
该map对应的每个元素Entry
对象中:key是ThreadLocal
对象的弱引用,value是该threadlocal
变量在当前线程中的对应的变量实体;
当某一线程执行获取该ThreadLocal
对象对应的变量时,首先从当前线程对象中获取对应的threadlocals
哈希表,再以该ThreadLocal
对象为key查询哈希表中对应的value;
由于每个线程独占一个threadlocals
哈希表,因此线程间ThreadLocal
对象对应的变量实体也是独占的,不存在竞争问题,也就避免了多线程问题。
ThreadLocalMap
的key
是弱引用,GC
之后key
是否为null
?在搞清楚这个问题之前,我们需要先搞清楚Java
的四种引用类型:
new
出来的对象就是强引用,只要强引用存在,垃圾回收器就永远不会回收被引用的对象,哪怕内存不足的时候。SoftReference
修饰的对象被称为软引用,在内存要溢出的时候软引用指向的对象会被回收。WeakReference
修饰的对象被称为弱引用,只要发生垃圾回收,被弱引用指向的对象就会被回收。PhantomReference
进行定。唯一的作用就是用来队列接受对象即将死亡的通知。这个问题的答案是不为null,从上图的图示就可以直接看出。
由图可知,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;
- }
ThreadLocal
的API
很少就包含了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
时指定初始值。针对这几个源码我们重点进行分析和体会。
- 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()
方法
基本流程可总结如下:
- 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
数据,如下图:
- 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()
方法,流程如下:
hash
计算下标。key
相等的元素,如果找到则做清除操作,引用置为null
,GC
的时候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();
- }
- }
- }
我们通过 ThreadLocal
实现了一个自定义的上下文管理组件 ContextManager
,并通过 ContextManager.set()
和 ContextManager.get()
方法在同一个线程中读写上下文中的状态数据。
现在,我们需要扩展这个功能,使其在一个线程执行过程中开启了一个 ContextManager
,随后使用线程池执行任务时,也能获取到当前 ContextManager
中的状态数据。这在如下场景中很常见:服务收到一个用户请求,通过 ContextManager
将登录态数据存储到当前线程的上下文中,随后使用线程池执行一些耗时操作,并希望线程池中的线程也能访问这些登录态数据。
由于线程池中的线程和请求线程不是同一个线程,按照目前的实现,线程池中的线程无法访问请求线程的上下文数据。
为了解决这个问题,我们可以在提交 Runnable
时,将当前的 ContextManager
引用存储在 Runnable
对象中。当线程池中的线程开始执行时,将 ContextManager
替换到执行线程的上下文中,执行完成后再恢复原来的上下文。
首先,添加静态方法 runWithExistingContext
和 supplyWithExistingContext
,用于在指定的上下文中执行任务:
- 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
是否能正确传递和恢复上下文信息。测试用例涵盖了两种情况:
这两种情况覆盖了在不同上下文环境中使用线程池的情况,确保了上下文信息能够正确传递和恢复。因此,验证内容是完备的,没有问题。
探讨如何基于 ThreadLocal
实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。通过具体的代码示例和实战展示 ThreadLocal
如何为多线程编程提供一种简洁而高效的上下文管理方案。
参考文章
https://www.cnblogs.com/wupeixuan/p/12638203.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。