zoukankan      html  css  js  c++  java
  • ThreadLocal 详解

    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);
            }
        }
    }
    
  • 相关阅读:
    LeetCode
    数据流中的中位数
    二叉搜索树的第k个结点
    对称的二叉树
    按之字形顺序打印二叉树
    把二叉树打印成多行
    二叉树的下一个结点
    链表中环的入口结点
    删除链表中重复的结点
    不用加减乘除做加法
  • 原文地址:https://www.cnblogs.com/loveer/p/11536648.html
Copyright © 2011-2022 走看看