专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

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中对象的引用关系图

71_1.png

从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的强引用不会消失,所以也不存在内存泄漏的问题,但是可能会有所浪费

文章永久链接:https://tech.souyunku.com/23806

未经允许不得转载:搜云库技术团队 » ThreadLocal源码解析

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们