ThreadLocal 源码解读

ThreadLocal 源码解读

入口

private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
contextHolder.set(value);

一切的开始都是从set方法为起点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lPvREJdI-1597399971550)(https://upload-images.jianshu.io/upload_images/2322338-6a296da2381e88a2.png?imageMogr2/auto-orient/strip|imageView2/2/w/714/format/webp)]

ThreadLocal#set

  • 实际上就是获取当前线程的threadLocals属性存储Entry,key为ThreadLocal实例,value为输入值,此处为强引用
  • 而ThreadLocal
  1. 获取当前线程的ThreadLocal.ThreadLocalMap实例属性

    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    1. 若为null:
    
    基于key(当前ThreadLocal实例),value(set传进来的value)创建一个ThreadLocalMap赋给线程。根据**threadLocalHashCode与length**的与运算结果确定位置
    
    1. 若不为null:

    ​ 调用ThreadLocalMap实例的set方法设置,key,value同上;详情看下面的set方法详解。

ThreadLocalMap

  • 数据结构采用 数组 + 开方地址法

  • Entry继承WeakReference,以 ThreadLocal 为 key,只能实现对 Reference 的 key 的回收,而对 value 的回收需要手动解决。

  • key的hashcode计算

    • hashCode 全部使用 threadLocalHashCode 字段,
    • threadLocalHashCode 用 final 修饰,不可变
    • threadLocalHashCode 的生成调用 nextHashCode()
    • 所有 ThreadLocalMap 的 hashCode 使用静态的 AtomicInteger 每次增加 1640531527(被称为魔数) 来产生

    set方法

     		1. 根据**threadLocalHashCode与length**的与运算结果确定位置 **i**,
              		2. 若table对应位置不为null,从**i**开始自增循环
          		1. 获取**i**位置的keyOld:
                        		1. keyOld == key : 直接替换即可
                        		2. keyOld == null : 替换
    

    扩容

    ​ threshold:最大长度的2/3,达到该比例容量就会触发rehash,rehash会先执行清除,回收后还不小于threshold则会触发扩容,容量增长为当前的2倍

总结

  • ThreadLocal实际上不存储数据,具体的内容存储在线程的属性threadLocals中,ThreadLocal实例仅仅是作为一个key;

  • ThreadLocal通过内部静态属性AtomicInteger来保证所有实例都采用同一套hashcode计算方法。

  • 线程的引用是强引用,ThreadLocal的引用是弱引用

    static class Entry extends WeakReference<ThreadLocal<?>> {
           
    
  • 线程采用线程池机制复用线程的前提下,若不考虑remove就会出现后面的线程可能会误用到前面线程遗留下来的ThreadLocalMap变量。

  • 线程不复用的前提下,正常流程结束的线程都不会出现内存泄漏问题,除非Thread一直存活而ThreadLocal实例到期了。

你可能感兴趣的:(JDK源码,java)