[Java并发] ThreadLocal原理

使用

    ThreadLocal<String> threadLocalA= new ThreadLocal<String>();
    threadLocalA.set(new String("A"));
    String str = threadLocalA.get();

原理

在每个线程的内部有个数据结构为Map的threadLocals属性,以的形式保存着线程变量和其对应的值。

当使用get()方法时,分为两步:

  1. 获取到当前线程的threadLocals,类型为Map。
  2. 以ThreadLocal为Map的key获取到它的value值。

源码

get()方法:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//当前线程为入参,获取当前线程的threadLocals
        if (map != null) {
            //入参为this,也就是说key为ThreadLocal
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;//threadLocals为线程的属性
    }

    private Entry getEntry(ThreadLocal key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);//避免内存泄漏,下文有提。
    }

内存泄漏

ThreadLocalMap结构如下:

static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {

            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k); //key,这个是一个弱引用,如果没有强引用来引用key(也就是ThreadLocal),则key会被回收,形成了key为null的Entry。
                value = v;
            }
        }

        private Entry[] table;
}

ThreadLocalMap是在线程内部的,故只要线程被回收了就不会存在内存泄漏。

ThreadLocal被回收时(key为null),没有办法获取到value,而线程又不会被回收时则value一直占用空间导致内存泄漏。线程不会被回收的常见场景是线程池。

JDK在此做了一个优化,在调用get(),set(),remove()方法会做额外处理来清理ThreadLocalMap中key为null的value,以减少内存泄漏的影响。但是如果key未使用弱引用,即时ThreadLocal被回收了,key也不为null,也就是说是没法判断哪个value需要回收的,最终造成内存泄漏。所以此处的弱引用key是内存泄漏的一个优化处理方式。

你可能感兴趣的:(Java,Java并发)