概述

  • 生命周期跟随Thread一起的一种存储临时变量的方式。
  • 是一种解耦传参的方式,让一些专属于线程的变量可以通过简易的方式获取。
  • 本质是线程的一个成员变量 ThreadLocalMap,而且这个Map使用开放寻址法实现的。
  • 广为人知的事

  • 声明一个ThreadLocal变量,会作为key。

  • 调用ThreadLocal中的api存储数据,其内部会把数据存储到当前线程Thread上的ThreadLocalMap属性中。
  • 存储的单位是Entry,ThreadLocal实例会作为这个Entry的key,真正的数据值会作为value。
  • 每次垃圾回收后,会把ThreadLocal清除,Entry的key就成了null
  • 每当get()set()方法时,如果原本位置上Entry的key为null,就会把key为null的值清理掉。

    问题

  • 首先在线程会被复用的情况下讨论这些才有意义(比如线程池),否则线程回收的同时它相关的一切都回收了,就不必纠结内存泄漏问题了。

  • ThreadLocal虽然是弱引用,但是仍然有引发内存泄漏的风险
  • 虽然ThreadLocal本身是弱引用,系统回收时会删除,但是ThreadLocal只是作为key存在ThreadThreadLocalMap中。
  • Map是以数组中Entry的形式存储数据的,Entry中ThreadLocal对象作为key,value对象作为值。
  • 也就是说,key是ThredaLocal,是弱引用,value却不是。
  • 经过一次垃圾回收后,ThreadLocalMap中会留下一下key为null,value还是原值的Entry
  • 无法被回收的value积累多了,就内存溢出了。

    解决

  • ThreadLocal实现本身有一定的解决能力,为null的ThreadLocal对应的key被触发get()set()方法时,会清理null值。

  • 最正确的解决方法
    • 每次用完ThreadLoca变量后调用remove()

重要源代码

ThreadLocalMap的存储实体Entry

  1. /**
  2. * The entries in this hash map extend WeakReference, using
  3. * its main ref field as the key (which is always a
  4. * ThreadLocal object). Note that null keys (i.e. entry.get()
  5. * == null) mean that the key is no longer referenced, so the
  6. * entry can be expunged from table. Such entries are referred to
  7. * as "stale entries" in the code that follows.
  8. */
  9. static class Entry extends WeakReference<ThreadLocal<?>> {
  10. /** The value associated with this ThreadLocal. */
  11. Object value;
  12. Entry(ThreadLocal<?> k, Object v) {
  13. super(k);
  14. value = v;
  15. }
  16. }

ThreadLocalMap中的set()方法

  1. /**
  2. * Set the value associated with key.
  3. *
  4. * @param key the thread local object
  5. * @param value the value to be set
  6. */
  7. private void set(ThreadLocal<?> key, Object value) {
  8. // We don't use a fast path as with get() because it is at
  9. // least as common to use set() to create new entries as
  10. // it is to replace existing ones, in which case, a fast
  11. // path would fail more often than not.
  12. Entry[] tab = table;
  13. int len = tab.length;
  14. int i = key.threadLocalHashCode & (len-1);
  15. for (Entry e = tab[i];
  16. e != null;
  17. e = tab[i = nextIndex(i, len)]) {
  18. ThreadLocal<?> k = e.get();
  19. if (k == key) {
  20. e.value = value;
  21. return;
  22. }
  23. if (k == null) {
  24. replaceStaleEntry(key, value, i);
  25. return;
  26. }
  27. }
  28. tab[i] = new Entry(key, value);
  29. int sz = ++size;
  30. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  31. rehash();
  32. }

ThreadLocalMap中的get()方法

  1. /**
  2. * Get the entry associated with key. This method
  3. * itself handles only the fast path: a direct hit of existing
  4. * key. It otherwise relays to getEntryAfterMiss. This is
  5. * designed to maximize performance for direct hits, in part
  6. * by making this method readily inlinable.
  7. *
  8. * @param key the thread local object
  9. * @return the entry associated with key, or null if no such
  10. */
  11. private Entry getEntry(ThreadLocal<?> key) {
  12. int i = key.threadLocalHashCode & (table.length - 1);
  13. Entry e = table[i];
  14. if (e != null && e.get() == key)
  15. return e;
  16. else
  17. return getEntryAfterMiss(key, i, e);
  18. }
  19. /**
  20. * Version of getEntry method for use when key is not found in
  21. * its direct hash slot.
  22. *
  23. * @param key the thread local object
  24. * @param i the table index for key's hash code
  25. * @param e the entry at table[i]
  26. * @return the entry associated with key, or null if no such
  27. */
  28. private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  29. Entry[] tab = table;
  30. int len = tab.length;
  31. while (e != null) {
  32. ThreadLocal<?> k = e.get();
  33. if (k == key)
  34. return e;
  35. if (k == null)
  36. expungeStaleEntry(i);
  37. else
  38. i = nextIndex(i, len);
  39. e = tab[i];
  40. }
  41. return null;
  42. }

ThreadLocalMap中的remove()方法

  1. /**
  2. * Remove the entry for key.
  3. */
  4. private void remove(ThreadLocal<?> key) {
  5. Entry[] tab = table;
  6. int len = tab.length;
  7. int i = key.threadLocalHashCode & (len-1);
  8. for (Entry e = tab[i];
  9. e != null;
  10. e = tab[i = nextIndex(i, len)]) {
  11. if (e.get() == key) {
  12. e.clear();
  13. expungeStaleEntry(i);
  14. return;
  15. }
  16. }
  17. }