• ThreadLocal源码解析


    在多线程的情况下,ThreadLocal提供了一个种为每个线程访问相同的变量,并且线程对变量的更新互不影响的机制。也是对象实现线程安全的一种方式。

    ThreadLocal的实现机制

    我们常用的方法有getsetinitialValue,这次将会围绕这几个方法的源码进行深入解析

    • get方法
        //  获取元素
        public T get() {
            //  当前线程
            Thread t = Thread.currentThread();
            //  通过当前线程获取ThreadLocalMap对象
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                //  获取Entry,其中key为ThreadLocal对象自身
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;        //  获取对象的值
                    return result;
                }
            }
            //  返回initialValue的值
            return setInitialValue();
        }
    

    首先,通过当前线程对象获取ThreadLocalMap对象,然后以ThreadLocal对象自身为key获取ThreadLocalMap.Entry,最后在获取Entry中的value

    代码的逻辑非常简单,我们再来看看getMapmap.getEntry方法

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

    threadLocals是Thread的一个属性

    public class Thread implements Runnable {
        //  ......
        //  threadLocals是Thread的一个属性
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    1. map.getEntry方法
      ThreadLocalMapThreadLocal对象的一个内部类,EntryThreadLocalMap的一个内部类
        //  ThreadLocal的内部类
        static class ThreadLocalMap {
            //  Entry是ThreadLocalMap的内部类,是一个弱引用对象
            static class Entry extends WeakReference<ThreadLocal<?>> {
                //  ThreadLocal中的value
                Object value;
                //  Entry的Key为ThreadLocal对象
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            //  Entry数组,用来存放一个线程的多个ThreadLocal变量
            private Entry[] table;
            //  根据ThreadLocal来获取对应的value
            private Entry getEntry(ThreadLocal<?> key) {
                //  通过hash算法获取key在数组中对应的下标
                int i = key.threadLocalHashCode & (table.length - 1);
                //  获取下标对应的Entry对象
                Entry e = table[i];
                //  获取value
                if (e != null && e.get() == key)
                    return e;
                else
                    //  当key不存在时获取值,有2中可能
                    //  1. 可能过期了
                    //  2. 可能扩缩容
                    return getEntryAfterMiss(key, i, e);
            }
    }
    

    key过期了如何获取值

            private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
    
                while (e != null) {
                    ThreadLocal<?> k = e.get();
                    //  如果key存在,直接返回value
                    if (k == key)
                        return e;
                    //  如果key为空说明已经过期了,需要清除
                    if (k == null)
                        expungeStaleEntry(i);
                    else
                        //  获取下一个key,看看能否找到
                        //  这是由于清除已经过期的key,
                        //  改变了Entry数组的size引起的位置变更
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }
    
    • set方法
    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    先获取ThreadLocalMap对象,然后在以ThreadLocalkey,将value设置到ThreadLocalMap对象中

    1. map.set方法
            //  将ThreadLocal对应的value存储到ThreadLocalMap对象中
            private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                //  计算table中的下标
                int i = key.threadLocalHashCode & (len-1);
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    // 获取ThreadLocal对象 
                    ThreadLocal<?> k = e.get();
                    //  如果Entry数组中存在ThreadLocal对象,则替换之前的值
                    if (k == key) {
                        e.value = value;
                        return;
                    }
                    //  去掉过期的ThreadLocal对象
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
                //  Entry数组中不存在ThreadLocal对象,创建一个新的Entry对象
                tab[i] = new Entry(key, value);
                int sz = ++size;
                //  清除过期的对象
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    // Entry数组的大小改变以后重新计算hash 
                    rehash();
            }
    
    1. createMap方法
    void createMap(Thread t, T firstValue) {
            //  当线程的threadLocals为null时,为线程初始化一个ThreadLocalMap对象
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    • initialValue方法
    // 可以通过重写该方法来返回默认值
    protected T initialValue() {
            return null;
        }
    

    ThreadLocal内存泄漏问题

    首先看一下ThreadLocal中对象的引用关系图

    ThreadLocal中对象的引用关系图

    从ThreadLocal中对象的引用关系来看,ThreadThreadLocalMapEntry对象之间都是强引用,如果可能出现内存泄漏那就是ThreadLocal对象弱引用引起的。

    什么时候会发生内存泄漏

    ThreadLocal实例不在有强引用指向,只有弱引用存在,且GC回收了这部分空间时,也就是Entry对象中的key被回收了,但是value还没有被回收,这时会出现内存泄漏,因为value无法得到释放。

    如何避免内存泄漏

    ThreadLocalMap中是通过expungeStaleEntrykeynull的对象对应的value也设置为null

    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
    
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;
    
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len);
            (e = tab[i]) != null;
            i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            //  如果key为null,会将value也设置成null
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                int h = k.threadLocalHashCode & (len - 1);
                if (h != i) {
                     tab[i] = null;
                    while (tab[h] != null)
                          h = nextIndex(h, len);
                   tab[h] = e;
                }
            }
        }
        return i;
    }
    

    所以只要调用expungeStaleEntry方法,且keynull时就可以回收掉value了,我们可以通过调用ThreadLocalremove方法进行释放

    避免ThreadLocal出现内存泄漏的方式有

    1. 调用ThreadLocalremove方法
    2. ThreadLocal变量定义成static类型的,对ThreadLocal的强引用不会消失,所以也不存在内存泄漏的问题,但是可能会有所浪费
  • 相关阅读:
    C#SortedList排序列表怎么样逆序输出
    使 SortList 实现重复键排序
    【转】delphi程序只允许运行一个实例的三种方法:
    Delphi中控制Excel(转载)
    spring和hibernate的集成
    使用Jedis操作redis
    使用java发送邮件
    error at ::0 can't find referenced pointcut...解决方法
    log4j简单的使用
    spring学习笔记三:Component注解(把POJO类实例化到spring的IOC容器中)
  • 原文地址:https://www.cnblogs.com/pinxiong/p/13288078.html
Copyright © 2020-2023  润新知