13.ThreadLocal

放肆的年华 提交于 2019-12-30 23:36:26

ThreadLocal类型的变量为线程本地变量,大概的原理就是建立了一个Map,然后key是线程,value是指,通过get方法就能取到每个线程自己的变量副本

1. ThreadLocal使用

2. ThreadLocal源码分析

public class ThreadLocal<T> {
    // 设置属性
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 找到当前线程里的map
        if (map != null)
            map.set(this, value); // 将 键值对 threadLocal的this 和 value 放入到当前线程的map中
        else
            createMap(t, value);
    }
    
    // 获取属性
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 获取map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); // 通过 threadLocal的this 获取到value
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    // 得到map
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // 创建map
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    //将数据从map中移除
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread()); // 通过 threadLocal的this 将键值对移除
        if (m != null) {
            m.remove(this);
        }
    }
}

通过源码分析,真正存储数据的地方是Thread的threadLocals 类型为ThreadLocalMap

  • key: threadLocald对象
  • value: 要存储的值

本来我以为ThreadLocal是一个map,里面存储了Thread和value的键值对,如下表
ThreadLocal

key value
线程1
线程2
线程3

但是真实的存是如下表
Thread

key value
threadLocal1
threadLocal2
threadLocal3

3. ThreadLocal问题

3.1. ThreadLoca置空无法回收的内存泄漏问题

使用ThreadLocal变量时,在使用完毕之后一定要调用remove方法将变量移除,不然会出现线程安全的问题。

就算移除了,然后把ThreadLocal local = null; 置空,但是还有Thread里的map持有local的引用,所以local指向的堆已经对我们不可用了,就产生了内存泄漏

记住是内存泄漏,不是内存溢出 而且泄露的是local对象本身。

但是这个问题已经使用弱引用解决了

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

虽然是Entry继承自WeakReference,但是只有key是若引用类型,这点一定要理解

当用户把local置空,引用断开,在回收的时候Entry里的引用也会断开,至此,local对应的堆已经没有引用指向了,所以会被gc回收掉

3.2. Value无法回收的内存泄漏问题

在上面我们解决了key内存泄漏的问题,但是同样的,就算key被置空,键值对变成了 null=value ,value还是不会被回收

而且value也没有使用弱引用,而是强引用,所以需要用户手动调用remove来移除value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!