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

news/2024/5/20 8:54:37 标签: java, 并发编程, JUC, ThreadLocal

ThreadLocal_0">ThreadLocal的用途

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

ThreadLocal_4">简单说下ThreadLocal的实现原理

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

ThreadLocalMapMap_9">讲一讲ThreadLocalMap这个Map呗

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

ThreadLocalkey_14">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_34">线程池中的线程使用ThreadLocal需要注意什么?

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

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

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

本文基于JDK8的源码分析。


http://www.niftyadmin.cn/n/768209.html

相关文章

JUC AtomicInteger源码解析 JDK8

前言 AtomicInteger类通过volatile语义加上CAS操作,使得对AtomicInteger的操作实现了一种非阻塞同步,从而保证了线程安全。非阻塞在于它没有使用synchronized或者Lock,而是循环加CAS,既然是循环执行,那么肯定没有阻塞…

JUC AtomicStampedReference源码解析 JDK8

前言 大家都知道CAS操作有ABA问题的&#xff0c;且ABA问题是针对引用型对象的&#xff0c;而AtomicStampedReference的出现就是为了解决这一问题而出现的。通过加版本号来实现。 JUC框架 系列文章目录 成员 public class AtomicStampedReference<V> {private static …

JUC AtomicIntegerArray源码解析 JDK8

前言 AtomicIntegerArray提供了对数组元素的原子操作&#xff0c;与其他非数组的原子类相比&#xff0c;它的成员不是volatile的而是final的了。 JUC框架 系列文章目录 成员 private static final Unsafe unsafe Unsafe.getUnsafe();private static final int base unsafe…

AQS深入理解 hasQueuedPredecessors源码分析 JDK8

文章目录前言流程hasQueuedPredecessors分析为什么先读取tail&#xff0c;再读取head先读取tail&#xff0c;再读取head的好处是否需要考虑 指令重排序虚假返回的true和false虚假返回true虚假返回false前言 Queries whether any threads have been waiting to acquire longer t…

Java并发 volatile可见性的验证

文章目录普通读 无法及时获得 主内存变量volatile读 及时获得 主内存变量普通读sleep普通读同步块同步块 遭遇 锁消除普通读System.out.println总结普通读 无法及时获得 主内存变量 public class volatileTest {static boolean flag false;//非volatile变量public static voi…

AQS深入理解 setHeadAndPropagate源码分析 JDK8

文章目录前言共享锁获取流程setHeadAndPropagate分析总结前言 Sets head of queue, and checks if successor may be waiting in shared mode, if so propagating if either propagate > 0 or PROPAGATE status was set. 此函数被共享锁操作而使用。这个函数用来将传入参数设…

AQS深入理解 doReleaseShared源码分析 JDK8

文章目录前言调用doReleaseShared的流程doReleaseShared分析head状态为0的情况特殊情况PROPAGATE状态设置后&#xff0c;并没有被检测到总结前言 Release action for shared mode – signals successor and ensures propagation. (Note: For exclusive mode, release just amou…

AQS深入理解 shouldParkAfterFailedAcquire源码分析 状态为0或PROPAGATE的情况分析

文章目录流程分析谁是调用shouldParkAfterFailedAcquire的线程什么时候会从parkAndCheckInterrupt中唤醒释放锁后&#xff0c;唤醒head后继的条件shouldParkAfterFailedAcquire源码分析关于shouldParkAfterFailedAcquire的疑问为什么检测到0后&#xff0c;一定要设置成SIGNAL&a…