ThreadLocal

        

目录

ThreadLocal 数据结构

Get 原理        

内存泄漏


        Java 中的 ThreadLocal 是一个线程的局部变量,它消除了多线程的数据读写争用,它一般用于一个线程在多个不同方法里的数据传递,比如会话登录,一请求一线程,Spring 事务等。

ThreadLocal 数据结构

        ThreadLocal 中有一个内部静态类 ThreadLocalMap,ThreadLocalMap 底层是一个 Entry 数组,Entry 是一个类,Entry 的 key 是弱引用类型的 ThreadLocal,value 是强引用类型的Object。ThreadLocalMap 是Thread 的一个实例属性,每一个 Thread 都有一个 ThreadLocalMap 实例对象,注意,这个ThreadLocalMap 不是全局唯一的。因为我们一般把 ThreadLocal  定义为 static final 类型,所以程序中一个 ThreadLocal对象是全局唯一的。数据结构决定功能。

public class ThreadLocal {
    ... // ThreadLocalMap 是 ThreadLocal 的内部静态类
    static class ThreadLocalMap { 
        // Entry 数组,一个元素就是一个 kv
        private Entry[] table;
        static class Entry extends WeakReference> { 
            Object value;
            // Entry 的 key 是 ThreadLocal 类型,它是弱引用,value 是强引用
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}
public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

        下面代码虽然 Entry 继承了 WeakReference 即 Entry 是 key 为 A 类型的弱引用,但即便 A 为空 Entry 也不会被立即回收,因为 value 是强引用。

    public static class Solution21 {
        static class Entry extends WeakReference {
            Object value;
            Entry(A key, Object value) {
                super(key);
                this.value = value;
            }

            @Override
            protected void finalize() throws Throwable {
                System.err.println("Entry 对象被回收...");
            }
        }

        static class A {
            @Override
            protected void finalize() throws Throwable {
                System.err.println(" A 对象被回收...");
            }
        }

        public static void main(String[] args) throws InterruptedException {
            A a = new A();
            // WeakReference aWeakReference = new WeakReference<>(a);
            Entry b = new Entry(a, "b");
            a = null;
            System.gc();
            Thread.sleep(1000); 
            System.out.println(b.value);
        }
    }

此时 key 被回收了,但还是能访问到 value,但已经不能通过 key 去访问 value 了,所以内存泄漏了。 

Get 原理        

        ThreadLocal#get() 会先通过当前线程获取本线程的实例属性 ThreadLocalMap,再通过全局唯一的 ThreadLocal 变量获取 value。 

public class ThreadLocal {
    public T get() {
        Thread t = Thread.currentThread();
        // 根据当前线程获取当前线程的实例属性 threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 根据 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;
    }
    static class ThreadLocalMap {
        private Entry getEntry(ThreadLocal key) {
            // 通过哈希定位到 Entry 位置
            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);
        }
    }
}
 

内存泄漏

        因为 Entry 的 key 是一个弱引用,如果它没有被强引用所引用,那它很容易就被 GC 导致值为 null,这时因为 value 是强引用不会被GC,但 value 又不可能通过 key 被外界访问,所以导致内存泄漏,内存泄漏多了就会发生内存溢出,程序崩溃。但我们可以通过 ThreadLocal#remove() 去回收 value 和 Entry,只要 key 为空的 Entry 程序会自动回收 value 并把非空的 Entry 再 hash 挪动位置,循环这个过程直到 Entry元素为空,所以用完了 ThreadLocal 后一定要手动 remove。 

public class ThreadLocal {
    public void remove() {
        // 获取当前线程的实例变量 ThreadLocalMap 
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
    static class ThreadLocalMap {
        private Entry[] table; 
        private int size = 0;
        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();
                    //只要执行了 expungeStaleEntry() 都会手动清除 key 为 null 的 Entry
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // 删除指定位置的Entry,expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            // 因为初始 i 位置的 tab[i] == null,所以放心遍历
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                // key 为 null 说明被key 被GC了,要回收该 value
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    // Entry 是有效的,但要再Hash挪动位置
                    int h = k.threadLocalHashCode & (len - 1);
                    // 如果 h == i 说明还是要在当前位置,不需要挪动位置
                    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;
        }
        // 移动到后一位,到最后一位循环到最前
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
    }
}

你可能感兴趣的:(java,开发语言)