ThreadLocal原理解析

ThreadLocal的定义

ThreadLocal原理解析

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

 

原理:Thread维护ThreadLocal与实例对象的映射

ThreadLocal是怎么达到线程隔离的呢?

以下源码片段可以看出存放对象映射关系是存放在Thread类中的threadLocals变量,存储类型是自定义的ThreadLocalMap

由ThreadLocal的get、set方法可以看出,map对象的key是ThreadLocal,value是ThreadLocalMap.Entry.value

//存放ThreadLocal与实例对象的映射变量
ThreadLocal.ThreadLocalMap threadLocals = null;


 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

public class ThreadLocal<T> {

    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();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
}

 

Entry是什么

看构造器可以看出ThreadLocal变量k被传递到了WeakReference的构造函数里面,也就是说key为ThreadLocal对象的弱引用,具体是referent变量引用了ThreadLocal对象,value为强引用,具体是调用ThreadLocal的set方法传递的值。

ThreadLocal原理解析

为什么要对key弱引用

使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免ThreadLocal 不能被回收而造成的内存泄漏的问题。但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。

关于引用:https://blog.csdn.net/WangMapleWang/article/details/87939611

 

怎么避免内存泄露

ThreadLocalMap提供了set,get,remove方法在一些时机下会对这些Entry项进行清理,但是这是不及时的,也不是每次都会执行的,所以在使用完毕后即使调用remove方法才是解决内存泄露主要办法

 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) {
                    e.clear();//清除key
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

//清除value=null的对象
private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }