美文网首页
ThreadLocal到底是个啥?

ThreadLocal到底是个啥?

作者: 面向对象架构 | 来源:发表于2022-12-26 00:26 被阅读0次

    ThreadLocal是什么

    提供线程局部变量,一个线程的局部变量在多个线程中有独立的副本,特点有:简单(开箱即用),快速(无额外开销),安全(线程安全);场景:多线程场景(资源持有,并发计算,线程一致性,线程安全)使用hash表实现,几乎所有提供多线程特征的语言都是其应用范围。

    ThreadLocal基本的API:带有泛型的构造函数,访问器get/set,初始化,回收。

    ThreadLocal原理

    ThreadLocal,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。ThreadLocalMap,用来存储数据,采用类似hashmap机制,存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。ThreadLocal,有个ThreadLocalMap类型的属性,存储的数据就放在这儿。

    ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,Thread有ThreadLocal.ThreadLocalMap类型的属性。

    源码盘点

    ThreadLocal源码目录
    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 getMap(Thread t) {
            return t.threadLocals;
        }
    
    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }
    

    ThreadLocalMap

    这个类在构造中创建了一个数组, new Entry[INITIAL_CAPACITY]; ,Entry里面就是一个object的对象,然后里面主要getEntry和set方法进行存取和读取。

        private void set(ThreadLocal<?> key, Object value) {
    
            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.refersTo(key)) {
                    e.value = value;
                    return;
                }
    
                if (e.refersTo(null)) {
                    replaceStaleEntry(key, value, i);  // 删除key为null的Entry
                    return;
                }
            }
    
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);//根据key获取位下标
            Entry e = table[i];  // 根据下标,获取这个Entry 里面是一个object,实现了软引用
            if (e != null && e.get() == key)
                return e; //校验没问题后返回
            else
                return getEntryAfterMiss(key, i, e);
        }
    
        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.refersTo(key)) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
    
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
    
            while (e != null) {
                if (e.refersTo(key))
                    return e;
                if (e.refersTo(null))          // 删除key为null的Entry
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
    

    使用场景

    四种核心场景:

    1、线程资源持有(ThreadLocalMap实现,持有线程资源供线程的各个部分使用,全局获取减少变成难度)

    2、线程资源一致性(帮助需要保持线程一致的资源,维护一致性,降低编程难度,例如:JDBC会话连接)

    3、线程安全(帮助只考虑了单线程的程序库,无遐想多线程场景迁移)

    4、分布式计算(帮助分布式计算场景的各个线程累计局部计算结果)

    ThreadLocal内存泄漏,如何避免

    内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次泄露危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。

    不再会被使用的对象或者变量占用的内存空间不能被回收,就是内存泄漏。

    强引用:使用最普遍的一个引用(new),一个对象具有强引用,不会被垃圾收集器回收。当内存空间不足,java虚拟机宁愿OOM,都不会回收。

    如果想取消强引用和某个对象之间的关联,可以显示将对象复制为null,这样jvm就会在安全区域执行gc进行垃圾回收。

    弱引用:jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用WeakReference类来表示。

    ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,ThreadLocalMap是由一个个Entry构成,而Entry继承了弱引用,key为使用弱引用的ThreadLocal对象,value为线程变量的副本。

    ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部的强引用时,Key(ThreadLocal对象)势必会被GC回收,这样就会导致ThreadLocalMap中的key为null,而value还存在着强引用,只有thread线程退出以后,value的强引用链才会断掉,但是如果线程迟迟不结速的话,这些key为null的Entry的value就会一直存在引用链。

    key使用强引用
    当ThreadLocalMap的key使用强引用时,此时若是外部的ThreadLocal对象被置为null,按理说应该被回收,但是ThreadLocalMap中还持有对ThreadLocal的强引用,如果没有手动删除,那么ThreadLocal不会被回收,导致Entry内存泄漏。

    key使用弱引用
    ThreadLocalMapkey为弱引用回收ThreadLocal对象时,由于ThreadLocalMap只持有ThreadLocal的弱引用,即使没有手动删除,也不会影响ThreadLocal的回收。当key为null时,在下一个调用ThreadLocalMap的setgetremove方法时会清除value值。

    ThreadLocal正确使用方法:

    • 每次使用完ThreadLocal都调用它的remove方法清楚数据
    • 将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal的强引用,也就能保证在任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,继续清除。

    相关文章

      网友评论

          本文标题:ThreadLocal到底是个啥?

          本文链接:https://www.haomeiwen.com/subject/jieixdtx.html