ThreadLocal 详解

欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot

package java.lang;

简介

ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个自己独立的变量副本。

对于同一个ThreadLocal,每个线程通过get、set、remove接口操作只会影响自身线程的数据,不会干扰其他线程中的数据。

使用场景:在每个线程希望有一个独有的变量时,解决线程间隔离与线程内共享的问题

内存泄漏

ThreadLocal内存泄漏的根源是:
    由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key的value就会导致内存泄漏。


弱引用作用:
    ThreadLocal被Entry中的Key弱引用,在没有强引用的情况下ThreadLocal会被回收,应此key变成null。
    对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,减少了内存泄漏的概率。


ThreadLocal以一个弱引用身份被Entry中的Key引用的(static class Entry extends WeakReference<ThreadLocal<?>>):

    因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。

    这个时候Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。

    因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,

    这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,

    这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。

JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:
    在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value(expungeStaleEntry(int staleSlot)),
    并将整个Entry设置为null,利于下次内存回收。


如果数据初始化好之后,一直不调用get、set等方法,这样Entry就一直不能回收,导致内存泄漏。所以一旦数据不使用最好主动remove()。

示例

public class Jdbc {
    //创建一个存储数据库连接对象的ThreadLocal线程本地变量
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    /**
     * 获取数据库的连接对象
     */
    public static Connection getConnection() {
        Connection conn = null;
        conn = tl.get();
        if (conn == null) {
            try {
                conn = DriverManager.getConnection();
                //将连接对象放入对应的ThreadLocal中(绑定到使用它的线程对象上)  
                tl.set(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 关闭数据库的连接,并删除对应的ThreadLocal中的对象
     */
    public static void closeConnection() {
        Connection conn = null;
        conn = tl.get();
        if (conn != null) {
            tl.remove();
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}  

源码分析

public class ThreadLocal<T> {

    protected T initialValue() {
        return null;
    }

    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    public ThreadLocal() {
    }

    //ThreadLocalMap以当前的threadLocal对象为key,get()方法时通过当前threadLocal实例就可以找到绑定在当前线程上的副本对象。
    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();
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        //获取当前线程的Map
        ThreadLocalMap map = getMap(t);
        //如果map不为空,以当前threadLocal对象为key,实际存储对象为value进行set操作
        if (map != null) {
            map.set(this, value);
        } else {
            //如果map为空,则创建ThreadLocalMap
            createMap(t, value);
        }
    }

    //每个Thread线程中都封装了一个ThreadLocalMap对象
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    ThreadLocalMap getMap(Thread t) {
        //"ThreadLocal.ThreadLocalMap threadLocals = null;"封装在Thread类中,每个线程都持有一个ThreadLocalMap变量
        return t.threadLocals;
    }

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }



    static class ThreadLocalMap {

        /**
        * Entry继承自WeakReference
        *      WeakReference(弱引用)的特性:
        *          从GCRoots出发的引用中没有有效引用指向该对象,则该对象就可以被回收。
        *          这里的有效引用并不包含WeakReference(弱引用是不可达对象引用),所以弱引用不影响对象被GC。
        */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

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

        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table;

        private int size = 0;

        private int threshold; // Default to 0

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        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);
        }
    }
}
posted @ 2019-09-17 20:06  LittleDonkey  阅读(371)  评论(0编辑  收藏  举报