ThreadLocal详解

两大使用场景——ThreadLocal的用途

  • 每个线程需要一个独享的对象(通常是工具类例如:SimpleDateFormat和Random),让某个需要用到的对象在线程间隔离
  • 每个线程内需要保存全局变量,可以让不同的方法使用,避免参数传递的麻烦

使用ThreadLocal带来的好处

  • 达到线程安全
  • 不需要加锁,提高执行效率
  • 更高效地利用内存、节省开销:相比于每个任务都新建一个SimpleDateFormat,显然用ThreadLocal可以节省内存和开销。
  • 免去传参的繁琐:无论是场景一的工具类还是场景二的全局变量,都可以在任何地方通过ThreadLocal直接拿到,不需要每次传递同样的参数。ThreadLocal使得代码耦合度更低,更优雅。

主要方法

  • T initialValue():初始化
    • 该方法会返回当前ThreadLocal对应的初始值,这是一个延迟加载,只有在get的时候才会调用initialValue方法
    • 线程在get之前调用了set方法,就不会调用initialValue方法
    • initialValue只会调用一次,除非使用了remove方法后调用get
    • 不重写initialValue方法,返回值是null。一般使用匿名内部类的方式来重写initialValue方法。
  • void set(T t):为这个ThreadLocal设置一个新值
  • T get():先取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal的引用作为参数传入,取出map中属于本ThreadLocal的value。如果是首次调用get(),则会调用initialValue来生成这个值,并创建一个map对象,并将生成的这个值与ThreadLocal类生成一个entry存入这个map中。
  • void remove():删除对应这个ThreadLocal的值。

ThreadLocal原理

  • ThreadLocalMap类
    ThreadLocalMap类,也就是Thread.threadLocals
    ThreadLocalMap类是每个线程Thread类里面的变量,里面最重要的是一个键值对数组Extry[] table,可以认为是一个map,键值对:

    • 键:这个ThreadLocal
    • 值:实际需要的成员变量,比如SimpleDateFormat对象。
    • 处理冲突:ThreadLocalMap采用的是线性探测法,也就是如果发生冲突,就继续找下一个空位置,而不是用链表拉链。
  • 两种场景都是调用的map.set()类设置值,也就是说,两种场景都会对应到ThreadLocalMap的一个Entry,只不过是起点和入口不一样。

ThreadLocal注意点

  • 内存泄漏

    • 什么是内存泄漏:某个对象不再有用,但是占用的内存却不能被回收。
    • Key的泄漏:ThreadLocalMap中的Entry继承自WeakReference,是弱引用,弱引用的特点是,如果这个对象被弱引用关联(没有任何强引用关联),那么这个对象就可以被回收
    • ThreadLocalMap的每个Entry都是一个对key的弱引用,同时每个Entry都包含了一个对value的强引用。正常情况下,当线程终止了,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了。但是如果线程不被终止(比如线程需要保持很久,例如线程池),那么key对应的value就不能被回收。 因为有调用链:Thread → ThreadLocalMap → Entry(key为null) → Value。会导致value无法回收,就可能会出现OOM。JDK已经考虑到了这个问题,所以在set,remove,rehash方法中会扫描key为null的Entry,并把对应的value置为null,这样value对象就可以被回收。但是如果一个ThreadLocal不被使用,那么实际上这些方法也不会被调用,就依然会有内存泄漏的隐患
    • 如何避免内存泄漏(阿里规约)
      调用remove方法,就会删除对应的Extry对象,可以避免内存泄漏,所以使用完ThreadLocal之后,应该调用remove方法。
  • ThreadLocal空指针异常问题

    • 若未初始化,get会返回一个null,若方法返回值涉及到装箱拆箱就可能导致空指针异常,并不是ThreadLocal的问题。
  • 共享对象

    • 如果在每个线程中ThreadLocal.set()保存的东西本来就是多线程共享的同一个对象,比如static对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
  • 如果可以不使用ThreadLocal就解决问题,那么就不要强行使用,比如任务数很少的时候,在局部变量中可以新建对象就可以解决问题,那么就不需要使用到ThreadLocal

  • 优先使用框架支持,而不是自己创造。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值