发布时间:2025-06-24 17:52:04  作者:北方职教升学中心  阅读量:552


商品信息和购买数量设置到上下文中。如果 ThreadLocalMap中不存在该值,则调用 setInitialValue()方法进行初始化。

这两种情况覆盖了在不同上下文环境中使用线程池的情况,确保了上下文信息能够正确传递和恢复。

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

    迭代到index=5的数据时,此时Entry.key=null,触发一次探测式数据回收操作,执行expungeStaleEntry()方法,执行完后,index 5、因为线程池中的线程会被多个请求重复使用,如何将 ThreadLocal中的上下文信息从主线程传递到线程池中的工作线程成为一个难题。

    (三)扩展 ContextManager的使用方式

    我们可以给 ContextManager添加类似的静态方法,以简化代码的书写。在 addToCart()方法中,我们开启了一个新的上下文,并将当前用户ID、

  • remove():从当前线程的 ThreadLocalMap中移除与当前 ThreadLocal对象对应的值,帮助防止内存泄漏。然而,如果需要维护的上下文信息较多,这种方式就显得笨拙且难以维护。

    为了解决这个问题,我们可以在提交 Runnable时,将当前的 ContextManager引用存储在 Runnable对象中。

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

    (一)定义 ContextManager 类

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

    (三)扩展 ContextManager 的使用方式

    三、 * @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类模拟了一个购物车服务。

  • 弱引用:使用WeakReference修饰的对象被称为弱引用,只要发生垃圾回收,被弱引用指向的对象就会被回收。在线程池中传递ContextManager

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

    (二)自定义线程池实现

    (三)测试自定义线程池

    四、

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

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

    由图可知,ThreadLocal.ThreadLocalMap 对应的Entry中,key为ThreadLocal对象的弱引用,方法执行对应栈帧中的ThreadLocal引用为强引用。它为每个线程提供了独立的变量副本,使得每个线程都能独立地操作自己的变量,而不会与其他线程的变量冲突。例如,当 Web 服务器收到一个请求时,需要解析当前登录状态的用户,并在后续的业务处理中使用这个用户名。

  • 针对这几个源码我们重点进行分析和体会。通过具体的代码示例和实战展示 ThreadLocal如何为多线程编程提供一种简洁而高效的上下文管理方案。在线程池中传递ContextManager

    我们通过 ThreadLocal实现了一个自定义的上下文管理组件 ContextManager,并通过 ContextManager.set()ContextManager.get()方法在同一个线程中读写上下文中的状态数据。get()、唯一的作用就是用来队列接受对象即将死亡的通知。

    package org.zyf.javabasic.thread.threadLocal;import org.zyf.javabasic.skills.reflection.dto.Product;/** * @program: zyfboot-javabasic * @description: 用户在进行购物时需要进行身份认证,并且在用户进行购物操作时,需要记录用户的购物车信息。

    目录

    一、

    (五)简单的直观体会

    以下是 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主要作用就是实现线程间变量隔离,对于一个变量,每个线程维护一个自己的实例,防止多线程环境下的资源竞争。我们可以使用 ContextManager类来管理用户的上下文信息。

    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.threadLocalsThreadLocalMap getMap(Thread t) {    return t.threadLocals;}// 调用ThreadLocalMap构造方法创建ThreadLocalMapvoid createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}// ThreadLocalMap构造方法,传入firstKey, firstValueThreadLocalMap(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相等的情况,另外一种就是不相等的情况。

    一个更加优雅的解决方案是使用 ThreadLocal来实现请求线程的上下文管理。

    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();        }    }}

    三、
  • withInitial(Supplier<? extends T> supplier):返回一个新的 ThreadLocal对象,其初始值由 Supplier提供。
  • 在新的上下文中执行任务,并使用自定义线程池执行任务。在 checkout()方法中,我们从当前上下文中读取了用户ID、

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

    我们先来看下Thread、

    二、如果只需要维护一个上下文数据,如用户名,可以通过方法传参的方式,将用户名作为参数传递给每个业务方法。通过具体的代码示例和实战展示 ThreadLocal如何为多线程编程提供一种简洁而高效的上下文管理方案。

    参考文章

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

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

    ThreadLocal原理 · 进击的java菜鸟

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

    滑动验证页面

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

  • 商品信息和购买数量,并执行了结账操作。ThreadLocalMap、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对象对应的值。测试用例涵盖了两种情况:

      1. 在当前上下文中执行任务,并使用自定义线程池执行任务。8的数据都会被回收,而index 6、

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

        不相等情况:不相等情况,以get(ThreadLocal2)为例计算下标为4,且4存在Entry,但key相等,这个时候则为往后迭代寻找key相等的元素,如果寻找过程中发现了有key = null的元素则回进行探测式清理操作。但是日常开发中会经常使用线程池等线程池化技术,释放线程对象的条件往往无法达到。当线程池中的线程开始执行时,将 ContextManager替换到执行线程的上下文中,执行完成后再恢复原来的上下文。基于Threadlocal实现的上下文管理组件ContextManager

        在实际开发中,我们经常需要维护一些上下文信息,这样可以避免在方法调用过程中传递过多的参数。

        Entry中value对应的是变量实体对象的强引用,因此释放一个ThreadLocal对象,是无法释放ThreadLocal.ThreadLocalMap中对应的value对象的,也就造成了内存泄漏。

        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进行上下文管理

        假设我们有一个在线商城系统,用户在进行购物时需要进行身份认证,并且在用户进行购物操作时,需要记录用户的购物车信息。

    这个问题的答案是不为null,从上图的图示就可以直接看出。ThreadLocal基本知识回顾分析

    (一)ThreadLocal原理

    ThreadLocal是 Java 提供的一个用于线程级别数据存储的类。虽然这种方式在某些情况下也能接受,但在使用线程池时,问题就变得复杂了。

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

    首先,添加静态方法 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是否能正确传递和恢复上下文信息。当方法执行过程中,由于栈帧销毁或者主动释放等原因,释放了ThreadLocal对象的强引用,即表示该ThreadLocal对象可以被回收了。又因为Entry中key为ThreadLocal对象的弱引用,所以当jvm执行GC操作时是能够回收该ThreadLocal对象的。总结

    探讨如何基于 ThreadLocal实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。

  • 软引用:使用SoftReference修饰的对象被称为软引用,在内存要溢出的时候软引用指向的对象会被回收。这在如下场景中很常见:服务收到一个用户请求,通过 ContextManager将登录态数据存储到当前线程的上下文中,随后使用线程池执行一些耗时操作,并希望线程池中的线程也能访问这些登录态数据。ThreadLocal基本知识回顾分析

    (一)ThreadLocal原理

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

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

    (四)部分核心源码回顾

    ThreadLocal.set()方法源码详解

    ThreadLocalMap.get()方法详解

    ThreadLocal.remove()方法源码详解

    (五)简单的直观体会

    二、remove()、这样,同一线程中的所有方法都可以通过 ThreadLocal对象直接读取和修改上下文信息,而无需在方法间传递参数。

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

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

    • 强引用:new出来的对象就是强引用,只要强引用存在,垃圾回收器就永远不会回收被引用的对象,哪怕内存不足的时候。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()、如果当前线程没有 ThreadLocalMap,则会创建一个新的 ThreadLocalMap并将值设置进去。

      JDK处理的方法是,在ThreadLocalMap进行set()

    • 虚引用:虚引用是最弱的引用,用PhantomReference进行定。

      现在,我们需要扩展这个功能,使其在一个线程执行过程中开启了一个 ContextManager,随后使用线程池执行任务时,也能获取到当前 ContextManager中的状态数据。总结


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

      探讨如何基于 ThreadLocal实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。

      (一)定义 ContextManager

      首先,定义一个 ContextManager类用于管理上下文信息。set()、这种机制特别适用于需要线程隔离的场景,通过 ThreadLocal,我们可以确保同一个变量在不同线程中拥有各自独立的值。

    • set(T value):将当前线程的 ThreadLocalMap中的值设置为给定的 value。当需要维护多个上下文状态时,可以使用多个 ThreadLocal实例来存储不同的信息。因此,验证内容是完备的,没有问题。这允许使用者在创建 ThreadLocal时指定初始值。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计算下标。当前请视业务情况进行应用和分析。

        一、

        四、除非释放当前线程对象,这样整个threadlocals都被回收了。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对象对应的变量实体也是独占的,不存在竞争问题,也就避免了多线程问题。