探索ThreadLocal

java

特色

  • ThreadLocal是一个线程内部的变量,只在本线程中使用,隔离其余线程
  • ThreadLocal内部维护了一个ThreadLocalMap
  • Thread内部引用了ThreadLocalMap
  • ThreadLocalMap能够保存键值对,可是一个ThreadLocal只能保存一个值,而且各个线程数据互不干扰
  • ThreadLocalMap存储时的key永远为当前的ThreadLocal
  • ThreadLocalMap存储时的key弱引用

ThreadLocalMap

每一个ThreadLocal只能存储一个数据,若是须要存储多个值的话,能够定义多个ThreadLocalThreadLocal在内部维护了一个ThreadLocalMap用来存储这些值。web

ThreadLocalMap并无去实现Map接口,它定义了一个Entry数组,每一个Entry<key,value>的形式来保存值,其中key为当前ThreadLocal自己,value为要保存的值。数组

注意Entry继承了WeakReference,它的key弱引用的,会被垃圾回收掉,因此会存在keynull的状况安全

ThreadLocalMap提供了三个方法:微信

  • set():以当前ThreadLocalkey存放值
  • get():以当前ThreadLocalkey获取存放的值
  • remove():清除数据

set()方法

  • 获取当前线程Thread.currentThread()
  • 获取当前线程的ThreadLocalMap
  • 判断ThreadLocalMap是否存在
  • 不存在的,经过createMap,初始化一个ThreadLocalMap,并赋值
  • 存在的,将当前ThreadLocal做为key,进行插入操做:
    • 经过ThreadLocal的哈希值获取要插入的位置
    • 若是当前位置的Entry为空,直接在该位置初始化一个Entry对象来实现插入操做;
    • 若是当前位置Entry的key和要设置的key相同,则覆盖原来的value
    • 若是当前位置Entry的key为null:
      • 循环获取下一位置
      • 若是key和要设置的key相同,则覆盖这个位置的值,并将这个位置和要插入点的entry互换,清理key为null的值;
      • 若是当前位置的Entry为null,退出循环,并在当前位置生成一个新的entry,并清理key为null的值;

get()方法

  • 获取当前线程Thread.currentThread()
  • 从当前线程获取ThreadLocalMap
  • 判断ThreadLocalMap是否存在
  • 不存在的调用setInitialValue进行初始化,并返回null;
  • 存在的,则以当前ThreadLocal做为key获取值:
    • 经过ThreadLocal的哈希值获取要获取值的位置
    • 当前位置的entry存在且key相同的,直接返回当前值;
    • 当前位置的entry存在且当前key为null的,执行清理重置方法
    • 循环获取下一位置的entry进行对比,若是下一位置的key相同,则返回该值;
    • 若是下一位置的entry为null,则说明该值不存在退出循环返回null;

remove()方法

清理当前ThreadLocal对应的Entry对象。并调用清理重置方法app

清理重置方法

  • 处理区间:Entry数组当前位置到下一个不为null的Entry之间的数据;
  • 清理key为null的Entry:value设为null,Entry设为null;
  • 重置key不为null的Entry:
    • 经过key的哈希值获取在Entry的数组的索引h;
    • 若是h和当前Entry的索引不一致进行位置重置:
      • 将当前位置的Entry设为null
      • 从h处开始日后找到首个Entry为null的位置
      • 将找到的新位置处的Entry设为原来的entry

Hash冲突

ThreadLocalMap并无实现Map接口,它不是经过链表的形式去避免Hash冲突的,而是经过后移的方式去实现。set方法时,若是当前要存放的位置的key和要设置的key不一致,则会对下一个位置进行判断,直到找到key相同或者为null或者Entrynull的位置。工具

内存泄漏问题

在实际的项目中,咱们的线程通常都是由线程池来管理的,线程会一直存在,ThreadLocalMap的value就有可能得不到回收,发送内存泄漏。为了处理这一问题,ThreadLocal的get()、set()方法都有可能会清除keynullEntry对象。安全起见,当咱们使用完后应该手动调用remove()方法清理掉数据。spa

用途

全局变量

某些数据好比用户ID,极可能在整条业务线上多个方法中都须要用到,若是经过方法参数的形式一层一层的传递下去,总体代码显得凌乱不优雅,这时能够经过ThreadLocal的方式存储。一般能够经过AOP或者拦截器的方式进行赋值,执行完业务逻辑以后调用remove()方法。线程

private final static ThreadLocal<UserInfo> TL_USER = new ThreadLocal<>();

TL_USER.set(userInfo);

UserInfo userInfo = TL_USER.get();

TL_USER.remove();
复制代码

独享对象

在实际项目中咱们一般会将时间相关的方法写在一个工具类中,每每会用到SimpleDateFormat进行格式化,它是线程不安全的。能够经过ThreadLocal来实现独享对象code

private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
复制代码

平常求赞

创做不易,若是各位以为有帮助,求点赞 支持


求关注

微信公众号: 俞大仙

相关文章
相关标签/搜索