ThreadLocal

1. 作用

  • 主要是用作数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,可以防止自己的变量被其他线程篡改

2. 应用场景

  • spring采用ThreadLocal,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以是业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
  • 在项目中一个线程经常遇到跨越若干方法调用,需要传递对象,即上下文信息,它是一种状态,经常时用户身份、任务信息等。所以可以在调用前在ThreadLocal中设置参数,其他地方调用get就可以得到上下文信息

3. 实现原理

  1. 每个线程对象拥有一个ThreadLocalMap类型的引用threadLocals,它是懒加载的,只有当第一次调用threadLocal实例的时候才初始化
  2. 这个threadLocals可以看成一个map,其中key为threadLocal实例,value为当前线程对应的存储信息。
    1. 调用set方法,就是以ThreadLocal对象自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
    2. 调用get方法,就是以ThreadLocal对象自己作为key,到当前线程关联的ThreadLocalMap集合中查找对应的value
    3. 调用remove方法,就是以ThreadLocal对象自己作为key,到关联的ThreaLocalMap中移除对应的键值对。

 1. ThreadLocal的使用

ThreadLocal<String> threadLocal= new ThreadLocal();
threadLocal.set("张三");
String name = threadLocal.get();
threadLocal.remove();

2. ThreadLocal的set方法

public void set(T value) {
    Thread t = Thread.currentThread();// 获取当前线程
    ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
    if (map != null) // 校验对象是否为空
        map.set(this, value); // 不为空则set
    else
        createMap(t, value); // 为空创建一个map对象并且进行set
}

  • 首先获取当前线程实例
  • 根据线程实例获取其ThreadLocalMap实例
  • 如果ThreadLocalMap实例不为空,则往这个map中存入value
  • 如果为空,则先创建实例,再往这个map中存入value

3. ThreadLocalMap如何解决Hash冲突

  • ThreadLocalMap里面包含了一个Entry数组,当用ThreadLoaclMap存储信息的时候,首先根据ThreaLocal对象的hash值定位到Entry数组的下标
    • 如果当前位置为空,则初始化一个Entry对象放在该位置上
    • 如果不为空,先判断是否当前位置的key是否为ThreadLocal对象相等,
      • 相等则刷新value
      • 不相等则去判断下一个位置(还是先判断是否为空,再判断是否相等),直到set成功

即通过开放地址法之线性探测法来解决hash冲突。(而HashMap是通过拉链法)

4. 如何共享线程的ThreadLocal数据

可以通过在主线程创建一个InheritableThreadLocal的实例,然后子线程可以调用这个实例的值

private void test() {
    final ThreadLocal threadLocal = new InheritableThreadLocal();
    threadLocal.set("张三");
    Thread t = new Thread() {
        @Override
        public void run() {
            super.run();
            Log.i( "name =" + threadLocal.get());
        }
    };
    t.start();
}

原理

  • Thead类中有一个inheritableThreadLocals变量
  • public
    class Thread implements Runnable {
    
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    }
  •  它用来保存父子共享的InheritableThreadLocal实例

4. ThreadLocal的内存泄露问题

  • ThreadLocal在保存的时候会把自己当作key存在ThreadLocalMap中,但是这个key被设计成了弱引用
  • 这就会导致,当ThreadLocal在没有外部强引用时,发生GC时这个key就会被回收。
  • 如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象的value就可能一直得不到回收,会发生内存泄漏问题
  • 在线程池中,一个线程处理一个任务完成后不会被销毁,而是保存起来留着以后复用,所以会导致ThreadLocalMap不会被清空,则就会发生ThreadLocal内存泄漏问题

如何解决

  • 在最后一次使用ThreadLocal后调用remove方法将值给清空就可以了。

5. 补充:jdk1.8 中改进的好处

  • 一般来说,一个Thread只会跟少数几个ThreadLocal关联,那么从Thread中去寻找对应的ThreadLocal的开销是比较小的
  • 如果一个Thread死掉,那么它关联的threadLocals也会被自动释放掉,很大程度上避免了内存泄漏的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值