美文网首页
我赌你不懂系列:你没用过ThreadLocal

我赌你不懂系列:你没用过ThreadLocal

作者: 分布式与微服务 | 来源:发表于2022-07-04 09:10 被阅读0次
    image.png

    前言

    先抛几个小问题吧。。。

    1. 如题,不会真有Java程序员没有用过ThreadLocal类吧?
    2. Java中的的引用类型有哪几种?
    3. ThreadLocal应用场景都有哪些?
    4. ThreadLocal会产生内存泄漏你了解吗? 啊? 啥是内存泄露?

    前段时间工作中做了一个Spring的动态数据源切换的小东西,是通过aop根据不同包下的请求来实现动态切换,为了防止多个线程同时请求的时候导致数据连接错乱就用到了ThreadLocal。

    这个怎么理解呢...比如线程a用的是a数据源,线程b用的b数据源,线程ab同时进入可能会导致a使用了b数据源。

    ThreadLocal

    如果你读过spring事务控制源码的话应该知道spring中的connection就有放在ThreadLocal中。

    ThreadLocal一般称为线程本地变量, 也就是说一个ThreadLocal的变量只有当前线程可以访问。

    每个线程都可以通过set()和get()来对这个局部变量进行操作,并不会和其他线程的局部变量发生冲突。

    总结一句话:当前线程使用ThreadLocal进行set的值只能当前线程通过get()获取到,别的线程不行。

    set

    接下来看一张图:

    image.png

    结合ThreadLocal的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);
        }
    
    

    在前面我们说调用ThreadLocal.set(Obj)能把Obj对象存储在当前线程空间内,通过代码可以看出其实不是将Obj放入ThreadLocal中,而是将Obj放在当前线程Thread当中的一个Map属性中,而这个map的key竟然就是调用set()的ThreadLocal对象。
    那我们来看下这个Map长什么样。

    public class Thread implements Runnable {
       ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    

    欧吼,又发现这个Map竟然是ThreadLocal类里的静态内部类。

    Map中的Entry应该没人看不懂吧.... 就是这个Map中有个属性Entry[] table,就是用来存我们的key value对。

    public class ThreadLocal<T> {   
        static class ThreadLocalMap {
                static class Entry extends WeakReference<ThreadLocal<?>> {
                    Object value;
    
                    Entry(ThreadLocal<?> k, Object v) {
                        super(k);
                        value = v;
                    }
                }
        }
    }
    
    

    注意! 这里的Entry竟然是WeakReference弱引用的子类!Entry的构造器中调用了WeakReference的构造器,导致每个Entry的key都是一个弱引用,Entry中的value依然是强引用。

    这个弱引用引用的是ThreadLocal对象内存空间,而我们在新建ThreadLocal对象的时候一般是new出来的,

    ThreadLocal<String> tl = new ThreadLocal<>();
    
    

    也有个变量tl强引用着这个ThreadLocal对象。

    内存泄露问题

    内存泄露 Memory Leak:该回收的垃圾对象没有被回收,发生了内存泄露,垃圾对象越堆越多,可用内存越来越少,若可用内存无法存放新的垃圾对象,就会导致内存溢出。
    内存溢出 Out Of Memory:当前创建的对象的大小大于可用的内存容量大小,发生内存溢出。
    内存泄露会导致内存溢出。

    当外部的强引用消失,如tl=null,那这个对象也就没啥意义了,现在只有我们的弱引用引用着这个对象内存空间了,它自然阻止不了垃圾回收掉这片空间。

    而我们Entry的value还是强引用啊,我key都没了,你给我留着value有啥用啊?这就会导致内存泄露,若可用内存无法存放新的垃圾对象,又会导致内存溢出。

    那怎么解决这个问题呢,我们只需要在使用完ThreadLocal后手动的调用ThreadLocal的remove方法就可以了。

    get

    get方法就是从先获取到当前Thread,然后拿到自身属性的map对象,根据ThreadLocal这个key去查看有没有对应Entry,有就获取value返回,没有则初始化一个value为null的Entry放入map中,返回null。

        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();
        }
    
    

    应用场景

    首先要明白使用ThreadLocal是以耗费内存为代价的。

    1. 在多层嵌套的方法中替代参数的显式传递

      也就是在上下文传递信息,线程内的所有方法都能获取到,避免一些参数传递。

    2. 多数据源动态切换

    相关文章

      网友评论

          本文标题:我赌你不懂系列:你没用过ThreadLocal

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