听说你看过ThreadLocal源码,来面试下这几个问题

ThreadLocal的用途

  • ThreadLocal用来给各个线程提供线程隔离的局部变量。使用很简单,通过调用同一个ThreadLocal对象的get/set方法来读写这个ThreadLocal对象对应的value,但是线程A set值后,不会影响到线程B之后get到的值。
  • ThreadLocal对象通常是static的,因为它在map里作为key使用,所以在各个线程中需要复用。

简单说下ThreadLocal的实现原理

  • 每个线程在运行过程中都可以通过Thread.currentThread()获得与之对应的Thread对象,而每个Thread对象都有一个ThreadLocalMap类型的成员,ThreadLocalMap是一种hashmap,它以ThreadLocal作为key。
  • 所以,只有通过Thread对象和ThreadLocal对象二者,才可以唯一确定到一个value上去。线程隔离的关键,正是因为这种对应关系用到了Thread对象。
  • 线程可以根据自己的需要往ThreadLocalMap里增加键值对,当线程从来没有使用到ThreadLocal时(指调用get/set方法),Thread对象不会初始化这个ThreadLocalMap类型的成员。

讲一讲ThreadLocalMap这个Map呗

  • 它是一种特殊实现的HashMap实现,它必须以ThreadLocal类型作为key。
  • 容量必为2的幂,使得它,可通过 位与操作得到数组下标。
  • 在解决哈希冲突时,使用开放寻址法(索引往后移动一位)和环形数组(索引移动到length时,跳转到0)。这样,只有size到达threshold时,才会resize操作。

ThreadLocal作为key,它的哈希值怎么计算的?

  • 利用魔数0x61c88647从0递增,得到每个ThreadLocal对象的哈希值。
  • 两个线程同时构造ThreadLocal对象,也能保证它俩的哈希值不同,因为利用了AtomicInteger。
  • 利用魔数0x61c88647的好处在于,这样得到的哈希值再取模得到下标,下标是均匀分布的。而这又可能带了另一个好处:当哈希冲突时,大概率能更快找到可以放置的位置。
  • 不要被魔数0x61c88647的网上示例迷惑,示例通常将魔数递增16次,再将每次递增的结果取模16,发现16次取模的结果(0-15)都不一样。这一点确实有点神奇,它利用了斐波那契数列(和黄金分割数),但是你把魔数定为0x01,一样能实现16次取模的结果(0-15)都不一样。重点在于,它能均匀分布。

为什么Entry继承了WeakReference?

  • 首先,WeakReference的父类成员referent,如果referent指向的对象没有强引用指着它,那么referent指向的对象就可能被回收,从而使得referent引用为null。
  • 而referent成员是作为key来使用的,这样key为null的entry(称为stale entry)在get/set操作中可能会被间接清理掉。
  • 所以,继承WeakReference的原因是为了能更快回收资源,但前提是:
    • 没有强引用指向ThreadLocal对象。
    • 且jvm执行了gc,回收了ThreadLocal对象,出现了stale entry。
    • 且之后get/set操作的间接调用刚好清理掉了这个stale entry。
  • 综上得知,要想通过WeakReference来获得更快回收资源的好处,其实比较难。所以,当你知道当前线程已经不会使用这个ThreadLocal对应的值时,显式调用remove将是最佳选择。

Entry继承了WeakReference,可能造成内存泄漏?

  • 首先要知道,这一点并不是WeakReference的锅。
  • 一般情况下,ThreadLocal对象都会设置成static域,它的生命周期通常和一个类一样长。
  • 当一个线程不再使用ThreadLocal读写值后,如果不调动remove,这个线程针对该ThreadLocal设置的value对象就已经内存泄漏了。且由于ThreadLocal对象生命周期一般很长,现在Entry对象、它的referent成员、它的value成员三者都内存泄漏。
  • 而Entry继承了WeakReference,反而降低了内存泄漏的可能性(见上一问题)。
  • 综上得知,内存泄漏不是因为继承了WeakReference,而且因为ThreadLocal对象生命周期一般很长,且使用完毕ThreadLocal后,线程没有主动调用remove

线程池中的线程使用ThreadLocal需要注意什么?

  • 由于ThreadLocalMap是Thread对象的成员,当对应线程运行结束销毁时,自然这个ThreadLocalMap类型的成员也会被回收。
  • 但如果想依赖上面这点来避免内存泄漏,就大错特错了。因为线程池里的线程为了复用线程,一般不会直接销毁掉完成了任务的线程,以下一次复用。
  • 所以,线程使用完毕ThreadLocal后,主动调用remove来避免内存泄漏,才是万全之策。
  • 另外,线程池中的线程使用完毕ThreadLocal后,不主动调用remove,还可能造成:get值时,get到上一个任务set的值,直接造成程序错误。

使用ThreadLocal有什么好处?(可以解决什么问题?)

  • 相比synchronized使用锁从而使得多个线程可以安全的访问同一个共享变量,现在可以直接转换思路,让线程使用自己的私有变量,直接避免了并发访问的问题。
  • 当一个数据需要传递给某个方法,而这个方法处于方法调用链的中间部分,那么如果采用加参数传递的方式,势必为影响到耦合性。而如果使用ThreadLocal来为线程保存这个数据,就避免了这个问题,而且对于这个线程,它在任何时候都可以取到这个值。

本文基于JDK8的源码分析。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值