Day Vision

Reading Everyday,Extending Vision

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

2023-02-28 04:25:47


set接下来看一张图:image.png结合ThreadLocal的set方法来看一下: public void set(T value) { Thread t = Thread.currentThread();


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. 多数据源动态切换