JAVA - ThreadLocal 相关小结

ThreadLocal

  1. ThreadLocal称为线程局部变量。每个线程独有的一块ThreadLocalMap空间,用来存储数据,不同线程间的ThreadLocalMap中数据是隔离的。
  2. ThreadLocalMap是个数组结构,由若干个Entry元素组成,初始大小为16。
  3. 一个Entry中 包括Key 和Value两部分。Key为弱引用类型,存储ThreadLocal对象的地址【hashcode】为键;value可存储任意对象。
  4. 线程中多个ThreadLocal 代表了多个key,在ThreadLocalMap中对应多个Entry。
  5. 获取ThreadLocalMap中数据时,需要在当前线程内使用ThreadLocal的get方法。
  6. ThreadLocal是线程安全的,因为每个ThreadLocal 在get set操作时,使用的本线程独享的Map容器。

PS:搬一张别人的图示意。

ThreadLocal使用场景

  1. 线程间数据隔离。比如管理Session、Cookie可以使用ThreadLocal做数据隔离。
  2. 跨层跨方法传参。使用第三方类库无法修改源码,且方法接口固定无法传参,可以使用ThreadLocal解决参数传递的问题。或进行跨层传参时,可以使用。
  3. 数据库连接管理。可用ThreadLocal保存数据库连接,使不同线程访问数据库时,使用的都是线程内独享的DB连接对象。

ThreadlocalMap内存溢出

  1. 在使用ThreadLocal对象的Set方法时,默认会调用expungeStaleEntry方法对Entry中Key为空的Value进行回收。
  2. 即便这样,依然推荐通过显示调用ThreadLocal对象Remove方法进行清理。

ThreadlocalMap哈希冲突如何解决

  1. 通过ThreadLocal的hashcode算出存入数组的位置。
  2. 如果对应位置的key为空,说明可使用,就存入【key、value】。
  3. 如果对应位置的key有值,且旧key和要存入的新key相等,则更新key对应的value。
  4. 如果对应位置的key有值,数组中的key和要存入的key不等,说明这个位置被使用【哈希冲突】了,向后寻找下一个空位置,直到找到。
  5. 如果没有找到,说明数组空间不够了,会调用方法清理不用的Entry,如果还不行,调用rehash方法,对数组进行扩容为原来的2倍。

PS:详细过程见ThreadLocal中Set方法。

如何解决子线程共享父线程局部变量?

在线程P中创建一个线程X,则X为线程P的子线程。这种场景使用ThreadLocal不适合,需要使用InheritableThreadLocal实现,见代码!

//父线程的线程变量,通过Inheritable TL共享给子线程
public class ThreadLocal_v3 {
    static InheritableThreadLocal parentVar = new InheritableThreadLocal();
    //static ThreadLocal parentVar = new ThreadLocal();
    public static void main(String[] args) throws InterruptedException {

        parentVar.set("This is parent varable !");
        System.out.println(parentVar.get());
        Thread childThread = new Thread(
                ()->{
                    System.out.println("Child Thread Begin !");
                    System.out.println(parentVar.get());
                }
        );
        childThread.start();
    }
}

ThreadLocalMap中的Value如何回收的?

  1. ThreadLocal对象在使用时,同时有两个引用指向它。一个是用户声明ThreadLocal对象时的强引用。【ThreadLocal tl = new ThreadLocal( )】
    另一个是ThreadlocalMap中的Key指向ThreadLocal对象所使用的WeakReference弱引用。
  2. 当用户使用ThreadLocal对象时,由于强引用存在,遭遇GC时,ThreadLocal对象不能被回收。
  3. 一旦用户使用完ThreadLocal后,指向ThreadLocal对象强引用不存在了,因此当GC时,弱引用Key指向的ThreadLocal对象就会被回收。【因为弱引用的特性就是一旦GC就会被回收】
  4. 被回收后的key变为null,此后ThreadLocalMap可以使用expungeStaleEntry方法对每个Entry中key为空,但value不为空的记录进行清理,设置value为空。

PS:再搬一张网上别人的图,比较直观…
在这里插入图片描述

ThreadLocalMap中key用强引用会怎样?

如果将key换成强引用,即便用户线程中不再有强引用指向ThreadLocal对象。当GC时,ThreadLocalMap中KEY引用指向的ThreadLocal对象也无法被回收。
造成expungeStaleEntry方法也无法完成key对应的value的回收,导致内存泄漏!

ThreadLocal可能存在的问题

  1. 当使用线程池时,或线程一直不消亡,或使用ThreadLocal时忘了调用Remove,可能会导致ThreadLocalMap中的value未得到释放,最后内存溢出。
  2. 使用ThreadLocalMap时如果存的数据太大、太多,当数组扩容时,会影响线程执行性能。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值