ThreadLocal原理及使用场景大揭秘( 四 )

因为nextHashCode被static修饰 , 所以每次new ThreadLocal()都会自增HASH_INCREMENT , 其值和斐波那契散列(Fibonacci)有关 , 主要目的是为了让哈希码能均匀的分布在2的n次方的数组里 。这也是为什么table的容量是2的n次方的一个原因 。

  1. 内存泄漏 & 弱引用 ThreadLocal使用不当可能会出现内存泄露 , 进而可能导致内存溢出** ,  内存泄露:垃圾对象没有及时回收或无法回收 , 一般情况下是因为对象有错误的引用 , 导致内存浪费 , 这些垃圾越来越多可能会导致内存溢出 , 内存溢出:没有足够的内存提供申请者使用 。当然了 , 任何操作不当都会出现内存泄露或其他bug , 我们这里只谈论ThreadLocal 。回顾Thread、ThreadLocal、ThreadLocalMap的关系 。Thread.threadLocals引用ThreadLocalMap , 生命周期一致 。ThreadLocal定义ThreadLocalMapThreadLocalMap#Entry弱引用ThreadLocal 。我们通常说一个对象不被引用就会被gc回收 , 其实说的是强引用 。但弱引用对象是 , 不管有没有被引用都会被垃圾回收 。当一个Thread执行完 , 被销毁后 , Thread.threadLocals指向的ThreadLocalMap实例也会随之变为垃圾 , 当然它里面存放的Entity也会被回收 。这时是不会发生内存泄漏的 。发生内存泄漏一般是在线程池 , Thread生命周期比较长 , threadLocals引用一直存在 , 当其存放的ThreadLocal被回收(弱引用生命周期比较短)后 , 它对应的Entity就成了key==null的实例 , 依然不会被回收 。如果此Entity一直不被get()、set()、remove()它就一直不会被回收 , 也就发生了内存泄漏 。通常在使用完ThreadLocal都会调用它的remove() 。补充:在ThreadLocal的get、set的时候 , 都会检查当前Entity的key是否为null , 如果是null就把Entity释放掉 , 被垃圾回收 。
应用场景它的应用场景主要有
  1. 线程安全 , 包裹线程不安全的工具类 , 比如java.text.SimpleDateFormat类 , 当然jdk1.8已经给出了对应的线程安全的类java.time.format.DateTimeFormatter
  2. 线程隔离 , 比如数据库连接管理、Session管理、mdc日志追踪等 。
最近在与前端对接的接口中用到了ThreadLocal 。大概流程是 , 前端在请求后端接口时在header带上toekn , 拦截器通过token获取到用户信息 , 通过ThreadLocal保存 。主要代码如下:
//接口请求时先走filterpublic boolean checkUserLogin(String token){   UserDTO user = getUserByToken(token);   ContextUtil.setUserId(user.getId());}public class ContextUtil {    private static ThreadLocal<String> userIdHolder = new ThreadLocal();    //存储userid    public static void setUserId(String userId) {        userIdHolder.set(userId);    }        public static String getUserId() {        return (String)userIdHolder.get();    }}    //实际调用接口void invokeInterface(){   String userId = ContextUtil.getUserId();   .....}每一次接口请求都是一个线程 , 在校验接口合法后把userid存入ThreadLocal , 以备后续之用 。
总结我们通过源码 , 对ThreadLocal的原理和应用作了深入讲解 。当然本人能力一般 , 水平有限 , 难免有些谬误 。还请各位多担待 , 欢迎指正 。有反馈才有进步 。

【ThreadLocal原理及使用场景大揭秘】


推荐阅读