常见面试题

  1. ThreadLocal有什么缺陷?为什么会导致内存泄漏?
  2. Entry的key为什么要用弱引用? 四种引用有什么区别说一下?为什么使用弱引用就可以解决内存泄漏问题?
  3. ThreadLocalMap的Key是什么?
  4. ThreadLocalMap如何解决value冲突问题?跟HashMap有什么区别?
  5. 进阶:ThreadLocal能否做到子线程中共享父线程中的数据?InheritableThreadLocal了解过吗?有什么局限性?
  6. 进阶:FastThreadLocal有了解过吗?

    ThreadLocal源码详解

  • 官方描述:
    该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
  • 自己的理解
    ThreadLocal线程本地变量。为每个线程创建一个变量副本。

    内部结构image.png

  • ThreadLocal由ThreadLocalMap组成,key为ThreadLocal;

  • 本质上:ThreadLocalMap包含了一个Entry数组;

关键方法

1. ThreadLocal#set()

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. //重要:根据线程获取Map
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. }

根据当前线程获取Map,如果Map为空,则createMap().

  • ThreadLocal#getMap()做了那些事情呢?

    1. ThreadLocalMap getMap(Thread t) {
    2. return t.threadLocals;//返回一个**线程绑定**的ThreadLocalMap
    3. }
  • ThreadLocal#createMap()做了那些事情呢?

    1. void createMap(Thread t, T firstValue) {
    2. t.threadLocals = new ThreadLocalMap(this, firstValue);
    3. }

    可以看出,其实Thread本身就内置了一个ThreadLocalMap。

那么接下来的重点就是看ThreadLocalMap如何初始化了。

1.1 创建ThreadLocalMap过程(初始化过程)

  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2. //初始化一个长度=16的对象数组。Entry extends WeakReference
  3. table = new Entry[INITIAL_CAPACITY];
  4. //确定Hash值
  5. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  6. //设值
  7. table[i] = new Entry(firstKey, firstValue);
  8. size = 1;
  9. setThreshold(INITIAL_CAPACITY);
  10. }

1.2 构造函数,创建Entry并设值 (重要!所以用截图)image.png

拓展:

  1. 为什么在ThreadLocalMap中弱引用Entry呢?会导致什么问题?
  2. 但是只有Key是弱引用的,当发生下一次GC时,

1.3 如何设值呢? 麻烦点:如果Hash冲突了怎么办呢?

  1. private void set(ThreadLocal<?> key, Object value) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. int i = key.threadLocalHashCode & (len-1);
  5. for (Entry e = tab[i];
  6. e != null;
  7. e = tab[i = nextIndex(i, len)]) {
  8. ThreadLocal<?> k = e.get();
  9. //
  10. if (k == key) {
  11. e.value = value;
  12. return;
  13. }
  14. if (k == null) {
  15. replaceStaleEntry(key, value, i);
  16. return;
  17. }
  18. }
  19. tab[i] = new Entry(key, value);
  20. int sz = ++size;
  21. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  22. rehash();
  23. }

解决方法:

线性探测

ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

2. ThreadLocal#get()

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

应用场景

数据库连接、Session管理

参考

  1. ThreadLocal-面试必问深度解析
  2. 【Java并发编程】深入分析ThreadLocal(八)