ThreadLocal几个要点

831 阅读2分钟

1 前言

ThreadLocal是java中的一个常用类,通常用来为当前线程存储变量。

2 创建

有三种创建方式:

  • 1 直接创建:ThreadLocal theadLocal = new ThreadLocal<>();
  • 2 创建时实现initialValue方法:
    ThreadLocal有一个initialValue方法,默认返回null,供子类创建时实现:
    protected T initialValue() {
        return null;
    }
    
    所以可以在创建时实现initialValue,以达到初始化数据的作用:
    public final static ThreadLocal<Object> theadLocal = new ThreadLocal<Object>(){
        @Override
        protected Object initialValue() {
            return new Object();
        }
    };
    
  • 3 ThreadLocal.withInitial: ThreadLocal的一个静态方法,通过一个Supplier在创建时初始化:
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    
    直接用withInitial来创建ThreadLocal:
    public static final ThreadLocal<Object> current = ThreadLocal.withInitial(() -> {
        return new Object();
    });
    

3 实现

3.1 ThreadLocalMap

每个Thread对象中,有一个ThreadLocal.ThreadLocalMap对象:

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal的操作(set/get/remove),就是对当前线程的ThreadLocal.ThreadLocalMap对象的操作:

Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

3.2 Entry

Entry是ThreadLocalMap内部存储数据的节点:

  • 1 Entry继承了WeakReference,referent为Entry的key(ThreadLocal对象);
  • 2 Entry的key是ThreadLocal对象。
static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
}

ThreadLocalMap中声明了Entry数组,作为数据的存储:

private static final int INITIAL_CAPACITY = 16;

private Entry[] table;

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 实例化Entry数组
            table = new Entry[INITIAL_CAPACITY];
            // 计算数组下标
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

数组的下标,由ThreadLocal.threadLocalHashCode做相关位运算后确定。

ThreadLocal.threadLocalHashCode在每个对象实例化时计算:

private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

4 移除

  • 1 取到当前线程的ThreadLocalMap,调用ThreadLocalMap.remove:
    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
    }
  • 2 根据key(ThreadLocal对象)找到对应的Entry,并执行Reference.clear()
 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    // 找到key,移除
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

5 关于内存泄露

  • 原因:
    • 虽然Entry节点继承了WeakReference,但是WeakReference在没有强引用时才会自动回收。
    • WeakReference -> Entry -> TreadLocalMap -> Thread 有一条强引用路径,而一般的线程会常驻内存,不会回收,所以WeakReference也不会自动回收。
  • 解决:手动remove();