ThreadLocal源码分析

242 阅读4分钟

ThreadLocal

常用方法

set/get/remove三个方法,这些方法都是操作Thread对象的ThreadLocalMap对象去实现的

set

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);//没有获取到ThreadLocalMap, 新建一个
    }

get

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//没有获取到ThreadLocalMap ,新建一个并赋值为初始化的值
    }

remove

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this); //this表示ThreadLocal对象
     }

数据结构

每个线程对象Thread中都有一个ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部静态类

Entry是继承自WeakReference<ThreadLocal<?>>,通过查看构造器中的super(k),发现Entry对象中的k是弱引用

可以发现,每个Thread对象中持有ThreadLocalMap对象,ThreadLocalMap是通过数组实现的,数组中的元素是Entry对象,Entry对象中key是ThreadLocal对象的弱引用,value是存储的值

既然是通过数组实现的,会获取ThreadLocal对象的哈希值并对数组长度取余来确定数组的索引下标,那么就可能出现哈希冲突,ThreadLocalMap是通过开放地址法来解决哈希冲突的。

内存泄漏

1. 第一种

上面提到了弱引用,这意味着,如果ThreadLocal没有被强引用的话,Entry中的k就会被GC会收掉,但是Entry中的value因为是强引用不会被回收掉。

那么 相当于有些Entry中k为null的value无法被访问到,如果线程不结束或者长时间不结束(比如使用线程池),而软引用被GC了,久而久之就会导致k为null的Entry越来越多,但是不会被使用,从而造成内存溢出

不过不需要担心,因为ThreadLocalMap中的set/get方法中用清空k为null的Entry的功能,因此忽略这种情况的

2. 第二种

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key(这里指的是ThreadLocal对象对应数组Entry元素)就会导致内存泄漏,而不是因为弱引用。

有这种说法,因为线程不销毁导致持有ThreadLocalMap对象不销毁,往里面添加很多key(ThreadLocal)导致内存泄漏

但是一个应用里面怎么会有那么多ThreadLocal对象呢?除非ThreadLocal不是静态的,线程没有销毁,很多地方每次都创建新的ThreadLocal然后使用,并且最后也没有remove掉。

通常我们使用ThreadLocal都定义成静态变量,从而防止第一种情况的弱引用被GC回收而获取不到Entry对应的value值

最终解决内存泄漏问题:

使用完了调用remove()方法移除ThreadLocalMap中的ThreadLocal对象

局限

ThreadLocal只能是自己线程存储和取值,子线程无法获取父线程存储的值

因此,出现了InheritableThreadLocal,不仅有ThreadLocal功能,还能获取父线程中存储的值

InheritableThreadLocal

源码分析

基本与ThreadLocal一样,新增了子线程获取父线程存储的数值功能

继承ThreadLocal并重写了三个方法

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;  //使用的是线程对象的inheritableThreadLocals属性
    }

    void createMap(Thread t, T firstValue) { //这个方法有用吗???感觉没用上
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

Thread新建的时候初始化操作

  if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

如果父线程中的inheritableThreadLocals不为空,那么新建,如何新建见下文:

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

基于父线程中的ThreadLocalMap新建当前线程的inheritableThreadLocals属性

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        //获取父线程的值,可以在这个方法修改父线程存储的值
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;//放入当前线程对象的数组中
                        size++;
                    }
                }
            }
        }

局限

从上述分析可以看到两个局限:

1. 在子线程中只能获取到新建子线程时候父线程存储的值

2. 子线程新建完成后,之后父线程的的设置、删除与子线程无关,子线程的的设置、删除与父线程无关

NamedThreadLocal

继承自ThreadLocal,新增了name属性

NamedInheritableThreadLocal

继承自InheritableThreadLocal,新增name属性