ThreadLocal原理剖析

简介: ThreadLocal原理剖析

1、基础概念


网络异常,图片无法展示
|


Stack & Heap


Stack和Heap是我们常说的栈和堆,这里不做赘述,只需要备注一点知识即可,Stack是线程私有独享且线程安全的,Heap是所有线程共享非线程安全的。TheadLocal的设计初衷就是希望让线程拥有了自己内部独享的变量,每个线程之间隔离互不干扰以起到线程安全的目的。


ThreadLocal


  • ThreadLocal是我们所说的线程本地变量。如上图所示,它的内部封装了一个非常重要的数据结构ThreadLocalMap来提供线程变量数据的真实获取、存储及移除等操作。我们可以把ThreadLocal理解称为一个封装类或是一个中介对象,所有的核心方法如get、set、remove等都通过ThreadLocal来提供和交互,而真正的幕后大佬是ThreadLocalMap这个封装了最终方法逻辑和内部数据结构的内部类。


private static AtomicInteger nextHashCode = new AtomicInteger();


private static final int HASH_INCREMENT = 0x61c88647;


private static int nextHashCode() {

   return nextHashCode.getAndAdd(HASH_INCREMENT);

}


  • ThreadLocal内部还持有一个实例变量threadLocalHashCode,它是一个哈希值用来做ThreadLocalMap中Entry哈希表路由计算的,threadLocalHashCode的生成方式是根据HASH_INCREMENT这个哈希魔数进行自加操作,关于哈希算法这部分后面会提到。


ThreadLocalMap


static class ThreadLocalMap {


   // hash map中的entry继承自弱引用WeakReference,指向threadLocal对象

   // 对于key为null的entry,说明不再需要访问,会从table表中清理掉

   // 这种entry被成为“stale entries”

   static class Entry extends WeakReference<ThreadLocal<?>> {

       /** The value associated with this ThreadLocal. */

       Object value;


       Entry(ThreadLocal<?> k, Object v) {

           super(k);

           value = v;

       }

   }


   private static final int INITIAL_CAPACITY = 16;


   private Entry[] table;


   private int size = 0;


   private int threshold; // Default to 0


   private void setThreshold(int len) {

       threshold = len * 2 / 3;

   }


   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

       table = new Entry[INITIAL_CAPACITY];

       int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

       table[i] = new Entry(firstKey, firstValue);

       size = 1;

       setThreshold(INITIAL_CAPACITY);

   }

}


  • ThreadLocalMap是一个自定义的hash map,专门用来保存线程的thread local变量
  • 它的操作仅限于ThreadLocal类中,不对外暴露
  • 这个类被用在Thread类的私有变量threadLocals和inheritableThreadLocals上
  • 为了能够保存大量且存活时间较长的threadLocal实例,hash table entries采用了WeakReferences作为key的类型
  • 一旦hash table运行空间不足时,key为null的entry就会被清理掉


Entry


这里的Entry继承了WeakReference类,它的内部构成是一个键值对结构,Key是弱引用的referant,是从WeakReference对象继承而来的,Value是实际存储的线程变量对象数据,即<K,V>=<Referant,Object>,而这里Entry进行了泛型限制,最终定义为Entry<ThrealLocal,Object>的数据格式


2、源码核心方法解析


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;

       }

   }

   return setInitialValue();

}


  • 获取当前线程内部的ThreadLocalMap
  • map存在则获取当前ThreadLocal对应的value值
  • map不存在或者找不到value值,则调用setInitialValue,进行初始化


setInitialValue()源码


private T setInitialValue() {

   T value = initialValue();

   Thread t = Thread.currentThread();

   ThreadLocalMap map = getMap(t);

   if (map != null)

       map.set(this, value);

   else

       createMap(t, value);

   return value;

}


  • 调用initialValue方法,获取初始化值【调用者通过覆盖该方法,设置自己的初始化值】
  • 获取当前线程内部的ThreadLocalMap
  • map存在则把当前ThreadLocal和value添加到map中
  • map不存在则创建一个ThreadLocalMap,保存到当前线程内部


get()方法时序图


网络异常,图片无法展示
|


set()源码


public void set(T value) {

   Thread t = Thread.currentThread();

   ThreadLocalMap map = getMap(t);

   if (map != null)

       map.set(this, value);

   else

       createMap(t, value);

}


  • 获取当前线程内部的ThreadLocalMap
  • map存在则把当前ThreadLocal和value添加到map中
  • map不存在则创建一个ThreadLocalMap,保存到当前线程内部


remove()源码


public void remove() {

   ThreadLocalMap m = getMap(Thread.currentThread());

   if (m != null)

    m.remove(this);

}


remove()方法时序图


网络异常,图片无法展示
|


3、核心算法


哈希值生成算法


哈希值生成算法:HASH_INCREMENT是参与哈希计算的哈希魔数,这里是16进制的0x61c88647,转换为10进制就是-1640531527,
每次自增HASH_INCREMENT进行生成,这里使用了AtomicInteger保证了线程安全


索引生成算法


索引生成算法:key.threadLocalHashCode & (len-1)


  • key.threadLocalHashCode即通过HASH_INCREMENT自增1得到的哈希值
  • len即当前哈希槽的容量,初始化默认是16,即时哈希槽扩容也是保持16的2倍,可以理解为2的N次方
  • 综上,索引生成是(HASH_INCREMENT自增HASH_INCREMENT)&(2的N次方-1)


黄金分割数


ThreadLocalMap采用黄金分割数的方式,大大降低了哈希冲突的情况。

网络异常,图片无法展示
|


  • 黄金分割数: int的黄金分割数是按照int的最大值2147483647乘以黄金分割比例Math.sqrt(5) - 1) / 2 计算得来的,最终int的黄金分割数为-1640531527。
  • 哈希魔数: 哈希数是通过哈希魔数HASH_INCREMENT加自身进行变更,经过研究发现这样会具有更好的散列性。
  • 高低位: (2的N次方-1)生成的数字均有一个特点,二进制高位为0,低位为1
  • 高效按位与: (HASH_INCREMENT自增HASH_INCREMENT)&(2的N次方-1)是索引生成的算法,因为是按位与运算,且(2的N次方-1)高位为0,低位为1,因此变成了HashCode &(2的N次方-1)= HashCode %(2的N次方-1)的运算,但是按位与运算效率要高于取模运算


(2的N次方-1)的二进制


2的1次方  num:1               binary:00000000000000000000000000000001

2的2次方  num:3               binary:00000000000000000000000000000011

2的3次方  num:7               binary:00000000000000000000000000000111

2的4次方  num:15               binary:00000000000000000000000000001111

2的5次方  num:31               binary:00000000000000000000000000011111

2的6次方  num:63               binary:00000000000000000000000000111111

2的7次方  num:127           binary:00000000000000000000000001111111

2的8次方  num:255           binary:00000000000000000000000011111111

2的9次方  num:511           binary:00000000000000000000000111111111

2的10次方    num:1023           binary:00000000000000000000001111111111

2的11次方    num:2047           binary:00000000000000000000011111111111

2的12次方    num:4095           binary:00000000000000000000111111111111

2的13次方    num:8191           binary:00000000000000000001111111111111

2的14次方    num:16383           binary:00000000000000000011111111111111

2的15次方    num:32767           binary:00000000000000000111111111111111

2的16次方    num:65535           binary:00000000000000001111111111111111

2的17次方    num:131071           binary:00000000000000011111111111111111

2的18次方    num:262143           binary:00000000000000111111111111111111

2的19次方    num:524287           binary:00000000000001111111111111111111

2的20次方    num:1048575     binary:00000000000011111111111111111111

2的21次方    num:2097151     binary:00000000000111111111111111111111

2的22次方    num:4194303     binary:00000000001111111111111111111111

2的23次方    num:8388607     binary:00000000011111111111111111111111

2的24次方    num:16777215     binary:00000000111111111111111111111111

2的25次方    num:33554431     binary:00000001111111111111111111111111

2的26次方    num:67108863     binary:00000011111111111111111111111111

2的27次方    num:134217727     binary:00000111111111111111111111111111

2的28次方    num:268435455     binary:00001111111111111111111111111111

2的29次方    num:536870911     binary:00011111111111111111111111111111

2的30次方    num:1073741823       binary:00111111111111111111111111111111


不难发现,2的幂次方-1的数字的二进制有一个特点那就是,高位都是0,,低位都是1。


(HASH_INCREMENT自增HASH_INCREMENT)的二进制


id:1   hashCode:1640531527   binary:01100001110010001000011001000111

id:2   hashCode:-1013904242   binary:11000011100100010000110010001110

id:3   hashCode:626627285   binary:00100101010110011001001011010101

id:4   hashCode:-2027808484   binary:10000111001000100001100100011100

id:5   hashCode:-387276957   binary:11101000111010101001111101100011

id:6   hashCode:1253254570   binary:01001010101100110010010110101010

id:7   hashCode:-1401181199   binary:10101100011110111010101111110001

id:8   hashCode:239350328   binary:00001110010001000011001000111000

id:9   hashCode:1879881855   binary:01110000000011001011100001111111

id:10   hashCode:-774553914   binary:11010001110101010011111011000110

id:11   hashCode:865977613   binary:00110011100111011100010100001101

id:12   hashCode:-1788458156   binary:10010101011001100100101101010100

id:13   hashCode:-147926629   binary:11110111001011101101000110011011

id:14   hashCode:1492604898   binary:01011000111101110101011111100010

id:15   hashCode:-1161830871   binary:10111010101111111101111000101001

id:16   hashCode:478700656   binary:00011100100010000110010001110000

id:17   hashCode:2119232183   binary:01111110010100001110101010110111

id:18   hashCode:-535203586   binary:11100000000110010111000011111110

id:19   hashCode:1105327941   binary:01000001111000011111011101000101

id:20   hashCode:-1549107828   binary:10100011101010100111110110001100

id:21   hashCode:91423699       binary:00000101011100110000001111010011

id:22   hashCode:1731955226   binary:01100111001110111000101000011010

id:23   hashCode:-922480543   binary:11001001000001000001000001100001

id:24   hashCode:718050984   binary:00101010110011001001011010101000

id:25   hashCode:-1936384785   binary:10001100100101010001110011101111

id:26   hashCode:-295853258   binary:11101110010111011010001100110110

id:27   hashCode:1344678269   binary:01010000001001100010100101111101

id:28   hashCode:-1309757500   binary:10110001111011101010111111000100

id:29   hashCode:330774027   binary:00010011101101110011011000001011

id:30   hashCode:1971305554   binary:01110101011111111011110001010010

id:31   hashCode:-683130215   binary:11010111010010000100001010011001


以上通过nextHashCode.getAndAdd(HASH_INCREMENT)生成32次hashCode。
由于(2的N次方-1)的二进制高位均为1,且是&运算,因此hashCode越具有散列性,最终索引值也会具有很好的散列性,哈希碰撞的可能性就会减少。
在(2的N次方-1)的二进制也就是length固定的情况下,低位都是1,高位都是0,因此,hashCode高位或低位相同太多会导致严重碰撞,一定要如上图这样到高低位都能具有很好的差异性参与计算才可以减少碰撞


id:1   hashCode:1640531527   index:7

id:2   hashCode:-1013904242   index:14

id:3   hashCode:626627285   index:21

id:4   hashCode:-2027808484   index:28

id:5   hashCode:-387276957   index:3

id:6   hashCode:1253254570   index:10

id:7   hashCode:-1401181199   index:17

id:8   hashCode:239350328   index:24

id:9   hashCode:1879881855   index:31

id:10   hashCode:-774553914   index:6

id:11   hashCode:865977613   index:13

id:12   hashCode:-1788458156   index:20

id:13   hashCode:-147926629   index:27

id:14   hashCode:1492604898   index:2

id:15   hashCode:-1161830871   index:9

id:16   hashCode:478700656   index:16

id:17   hashCode:2119232183   index:23

id:18   hashCode:-535203586   index:30

id:19   hashCode:1105327941   index:5

id:20   hashCode:-1549107828   index:12

id:21   hashCode:91423699       index:19

id:22   hashCode:1731955226   index:26

id:23   hashCode:-922480543   index:1

id:24   hashCode:718050984   index:8

id:25   hashCode:-1936384785   index:15

id:26   hashCode:-295853258   index:22

id:27   hashCode:1344678269   index:29

id:28   hashCode:-1309757500   index:4

id:29   hashCode:330774027   index:11

id:30   hashCode:1971305554   index:18

id:31   hashCode:-683130215   index:25

id:32   hashCode:957401312   index:0


以上是length为32时,生成的32次索引值的情况,发现索引值分布非常均匀,没有出现碰撞。


哈希冲突解决


当出现哈希冲突时,它的做法看是否是同一个对象或者是是否可以替换,否则往后移动一位,继续判断,这里采用的是再次寻址的方法。


private static int nextIndex(int i, int len) {

   return ((i + 1 < len) ? i + 1 : 0);

}


内存清理


ThreadLocal之所以采用Entry的key使用弱引用就是为了尽快回收避免大量占用内存空间,除此之外还创造性的增加了探测式清理、启发式清理两种方式在核心方法get、set等调用时进行内存对象的回收和清理工作


探测式清理


private int expungeStaleEntry(int staleSlot) {

   Entry[] tab = table;

   int len = tab.length;

 

   // 因为entry对应的ThreadLocal已经被回收,value设为null,显式断开强引用

   tab[staleSlot].value = null;

   // 显式设置该entry为null,以便垃圾回收

   tab[staleSlot] = null;

   size--;

 

   Entry e;

   int i;

   for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {

       ThreadLocal<?> k = e.get();

       // 清理对应ThreadLocal已经被回收的entry

       if (k == null) {

           e.value = null;

           tab[i] = null;

           size--;

       } else {

           /*

            * 对于还没有被回收的情况,需要做一次rehash。

            *

            * 如果对应的ThreadLocal的ID对len取模出来的索引h不为当前位置i,

            * 则从h向后线性探测到第一个空的slot,把当前的entry给挪过去。

            */

           int h = k.threadLocalHashCode & (len - 1);

           if (h != i) {

               tab[i] = null;

               

               /*

                * 在原代码的这里有句注释值得一提,原注释如下:

                *

                * Unlike Knuth 6.4 Algorithm R, we must scan until

                * null because multiple entries could have been stale.

                *

                * 这段话提及了Knuth高德纳的著作TAOCP(《计算机程序设计艺术》)的6.4章节(散列)

                * 中的R算法。R算法描述了如何从使用线性探测的散列表中删除一个元素。

                * R算法维护了一个上次删除元素的index,当在非空连续段中扫到某个entry的哈希值取模后的索引

                * 还没有遍历到时,会将该entry挪到index那个位置,并更新当前位置为新的index,

                * 继续向后扫描直到遇到空的entry。

                *

                * ThreadLocalMap因为使用了弱引用,所以其实每个slot的状态有三种也即

                * 有效(value未回收),无效(value已回收),空(entry==null)。

                * 正是因为ThreadLocalMap的entry有三种状态,所以不能完全套高德纳原书的R算法。

                *

                * 因为expungeStaleEntry函数在扫描过程中还会对无效slot清理将之转为空slot,

                * 如果直接套用R算法,可能会出现具有相同哈希值的entry之间断开(中间有空entry)。

                */

               while (tab[h] != null) {

                   h = nextIndex(h, len);

               }

               tab[h] = e;

           }

       }

   }

   // 返回staleSlot之后第一个空的slot索引

   return i;

}


  • 根据场景改进了高德纳论述的从使用线性探测的散列表中删除一个元素的R算法
  • 如果index对应的slot就是要读的threadLocal,则直接返回结果
  • 调用getEntryAfterMiss线性探测,过程中每碰到无效slot,调用expungeStaleEntry进行段清理;如果找到了key,则返回结果entry
  • 没有找到key,返回null


启发式清理


private boolean cleanSomeSlots(int i, int n) {

   boolean removed = false;

   Entry[] tab = table;

   int len = tab.length;

   do {

       // i在任何情况下自己都不会是一个无效slot,所以从下一个开始判断

       i = nextIndex(i, len);

       Entry e = tab[i];

       if (e != null && e.get() == null) {

           // 扩大扫描控制因子

           n = len;

           removed = true;

           // 清理一个连续段

           i = expungeStaleEntry(i);

       }

   } while ((n >>>= 1) != 0);

   return removed;

}


  • 启发式地清理slot,i对应entry是非无效(指向的ThreadLocal没被回收,或者entry本身为空),n是用于控制控制扫描次数的
  • 正常情况下如果log n次扫描没有发现无效slot,函数就结束了
  • 但是如果发现了无效的slot,将n置为table的长度len,做一次连续段的清理,再从下一个空的slot开始继续扫描
  • 这个函数有两处地方会被调用,一处是插入的时候可能会被调用,另外个是在替换无效slot的时候可能会被调用,区别是前者传入的n为元素个数,后者为table的容量


4、缺点


内存泄露问题


网络异常,图片无法展示
|

如上图,整理对象引用关系,用**++> 表示强引用,用 –>**表示弱引用


  • Thread ++> ThrealLocal.ThreadLocalMap ++> Entry ++> key (referant) --> ThreadLocal
  • Thread ++> ThrealLocal.ThreadLocalMap ++> Entry ++> value ++> Object
    由于key (referant) --> ThreadLocal是弱引用,gc时会回收key,此时key为null,但是Thread作为这条引用链的Root根不会立刻线程执行完毕而消失,会一直驻留在Stack中,这种情况一般可以有两种,一个事在一个for循环中执行,一种是线程池中执行,因此会导致强引用的value不会进行释放对象导致内存溢出


父子线程无法传递线程副本数据


在主线程中使用ThreadLocal无法直接传递给子线程,如果要操作还需要通过线程封闭进行变量置换。


5、问题汇总


  • ThreadLocal的key是弱引用,那么在 threadLocal.get()的时候,发生GC之后,key是否为null?
    由于Entry<WeakReference,Object>的key是弱引用,GC后就会被回收
  • ThreadLocal中ThreadLocalMap的数据结构?
    哈希表
  • ThreadLocalMap的Hash算法?
    key.threadLocalHashCode & (length -1) , length为2的幂次方
  • ThreadLocalMap中Hash冲突如何解决?
    开放地址,二次寻址,由于使用黄金分割数进行哈希计算,散列非常好,出现碰撞的可能性很低,所以没有像HashMap那样进行链地址解决冲突
  • ThreadLocalMap扩容机制?
    length2/3 触发rehash逻辑,进行探测式清理,最终判断size >= threshold 3/4来决定是否要真正扩容调用resize方法
  • ThreadLocalMap中过期key的清理机制?探测式清理和启发式清理流程?
    探测式清理(expungeStaleEntry())、启发式清理(cleanSomeSlots())
    探测式清理是以当前Entry 往后清理,遇到值为null则结束清理,属于线性探测清理,结合了
  • ThreadLocalMap.set()方法实现原理?
  • ThreadLocalMap.get()方法实现原理?
  • 项目中ThreadLocal使用情况?遇到的坑?
    父子线程不能传递线程变量,主线程中使用线程池相当于父线程中使用子线程无法传值


6、实战应用


复杂场景


Spring容器、RPC全链路traceId传递等


应用举例


在单体应用中一般不会声明多个ThreadLocal,即不会让Thread中持有的ThreadLocalMap的key有多个,由于ThreadLocal是支持泛型的,我们可以传入一个线程安全的容器,让Thread内持有的ThreadLocalMap中只有一个key,即只有一个ThreadLocal的key引用,而存储的Object可以是一个Map或者List,我们根据业务场景操作容器即可,大部分情况都可以满足,设计合理的话是可以共用的,减少持有key也不会占用大量的栈空间,且把ThreadLocal声明为private final static,下面提供一个demo:


/**

* @author: guanjian

* @date: 2020/07/08 9:31

* @description: 环境变量

*/

@Component("contextHolder")

public class ContextHolder<T, R> {


   private final static Logger LOGGER = LoggerFactory.getLogger(ContextHolder.class);

   /**

    * 入参对象

    */

   public final static String REQUEST_PARAM = "request_param";


   /**

    * 出参对象

    */

   public final static String RESPONSE_PARAM = "response_param";


   /**

    * 传值对象

    */

   public final static String TRANSMIT_PARAM = "transmit_param";


   /**

    * 线程变量

    */

   private final static ThreadLocal<Map<Object, Object>> localVariable = ThreadLocal.withInitial(() -> Maps.newHashMap());


   public void bindLocal(Object key, Object value) {

       Objects.requireNonNull(key, "key can not be null");


       Map holder = localVariable.get();


       holder.put(key, value);


       localVariable.set(holder);


       LOGGER.debug("[ContextHolder] key={},value={} binded.", key, JSON.toJSONString(value));

   }


   public Object getLocal(Object key) {

       if (CollectionUtils.isEmpty(localVariable.get())) return null;


       Object value = localVariable.get().get(key);


       LOGGER.debug("[ContextHolder] key={},value={} getted.", key, JSON.toJSONString(value));

       return value;

   }


   public void bindRequest(T value) {

       bindLocal(REQUEST_PARAM, value);

   }


   public T getRequest() {

       return (T) localVariable.get().get(REQUEST_PARAM);

   }


   public void bindResponse(R value) {

       bindLocal(RESPONSE_PARAM, value);

   }


   public R getResponse() {

       return (R) localVariable.get().get(RESPONSE_PARAM);

   }


   public void bindTransmit(Object value) {

       bindLocal(TRANSMIT_PARAM, value);

   }


   public Object getTransmit() {

       return getLocal(TRANSMIT_PARAM);

   }


   public void clear() {

       localVariable.remove();

   }

}


7、参考


https://www.cnblogs.com/wang-meng/p/12856648.html
https://blog.csdn.net/zjcsuct/article/details/104310194
http://www.iocoder.cn/JDK/ThreadLocal/
https://blog.csdn.net/qq_22167989/article/details/89448670

相关文章
|
7月前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
88 0
|
4月前
|
存储 设计模式 安全
深入理解ThreadLocal原理
本文深入探讨了Java中的ThreadLocal及其内部数据结构ThreadLocalMap的工作原理和特性,帮助读者理解如何利用ThreadLocal实现线程局部变量的隔离和线程安全。
深入理解ThreadLocal原理
|
7月前
|
存储 安全 Java
ThreadLocal原理讲解
ThreadLocal原理讲解
59 0
|
7月前
|
设计模式 缓存 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理3
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
1030 1
|
7月前
|
存储 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理2
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
726 0
|
存储 Java
大厂是怎么用ThreadLocal?ThreadLocal核心原理分析
ThreadLocal**是Java中的一个线程本地变量类。它可以让每个线程都有自己独立的变量副本,而不会相互影响。
125 1
|
Java 数据库连接
ThreadLocal原理和实践
ThreadLocal是线程本地变量,解决多线程环境下成员变量共享存在的问题。ThreadLocal为每个线程创建独立的的变量副本,他的特性是该变量的引用对全局可见,但是其值只对当前线程可用,每个线程都将自己的值保存到这个变量中而各线程不受影响。
165 0
ThreadLocal原理和实践
|
存储 SpringCloudAlibaba Java
浅析ThreadLocal使用及实现原理
提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其`get` 或 `set`方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。`ThreadLocal`实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联 。所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以这么说Th
117 0
浅析ThreadLocal使用及实现原理
|
存储 安全 Java
ThreadLocal源码分析
ThreadLocal,即线程局部变量。主要用于线程间数据隔离。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
|
Java 定位技术
ThreadLocal原理
经典八股文之ThreadLocal原理
201 0