[关闭]
@Yano 2018-12-23T15:05:09.000000Z 字数 3423 阅读 730

ThreadLocal 源码分析

Java


Demo

  1. public class ThreadLocalMain {
  2. public static void main(String[] args) {
  3. ThreadLocal<String> threadLocal = new ThreadLocal<>();
  4. threadLocal.set("hello, main");
  5. System.out.println(threadLocal.get());
  6. ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
  7. threadLocal2.set("hello, main2");
  8. System.out.println(threadLocal2.get());
  9. }
  10. }

输出

  1. hello, main
  2. hello, main2

源码分析

ThreadLocal 类上的注释:

  1. This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
  2. For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.
  3. import java.util.concurrent.atomic.AtomicInteger;
  4. public class ThreadId {
  5. // Atomic integer containing the next thread ID to be assigned
  6. private static final AtomicInteger nextId = new AtomicInteger(0);
  7. // Thread local variable containing each thread's ID
  8. private static final ThreadLocal<Integer> threadId =
  9. new ThreadLocal<Integer>() {
  10. @Override protected Integer initialValue() {
  11. return nextId.getAndIncrement();
  12. }
  13. };
  14. // Returns the current thread's unique ID, assigning it if necessary
  15. public static int get() {
  16. return threadId.get();
  17. }
  18. }
  19. Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

成员变量

ThreadLocal类中有一个threadLocalHashCode,作为ThreadLocalMap中key(ThreadLocal)的哈希值。

  1. private final int threadLocalHashCode = nextHashCode();

ThreadLocalMap是ThreadLocal的内部类,是一个自定义的HashMap。注意ThreadLocalMap和HashMap实现原理有很大差别:

  1. HashMap是取key对象的哈希值,ThreadLocalMap是取key对象(ThreadLocal)的threadLocalHashCode变量
  2. 解决hash冲突:HashMap是通过链表法,ThreadLocalMap是通过开放地址法。

其set核心代码如下,for循环中对table[i]进行判断,key是该ThreadLocal则修改其value,否则其为null时再放入。

  1. private void set(ThreadLocal<?> key, Object value) {
  2. // We don't use a fast path as with get() because it is at
  3. // least as common to use set() to create new entries as
  4. // it is to replace existing ones, in which case, a fast
  5. // path would fail more often than not.
  6. Entry[] tab = table;
  7. int len = tab.length;
  8. int i = key.threadLocalHashCode & (len-1);
  9. for (Entry e = tab[i];
  10. e != null;
  11. e = tab[i = nextIndex(i, len)]) {
  12. ThreadLocal<?> k = e.get();
  13. if (k == key) {
  14. e.value = value;
  15. return;
  16. }
  17. if (k == null) {
  18. replaceStaleEntry(key, value, i);
  19. return;
  20. }
  21. }

set

获取当前线程的ThreadLocalMap,对其进行set操作。

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

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. }

ThreadLocal 是否会内存泄漏?

会。ThreadLocal中table是一个Entry对象的数组,而Entry是一个ThreadLocal的弱引用。

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4. Entry(ThreadLocal<?> k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

当 Key 为 null 时,该条目就变成“废弃条目”,相关“value”的回收,往往依赖于几个关键点,即 set、remove、rehash。

弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

ThreadLocal内存泄漏(其实不算是内存泄漏,只能算是内存不足而已)的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。(参考自:深入分析 ThreadLocal 内存泄漏问题

ThreadLocal 的最佳实践

废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应 ThreadLocalMap!应用一定要自己负责 remove,并且不要和线程池配合。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注