前言
先抛几个小问题吧。。。
- 如题,不会真有Java程序员没有用过ThreadLocal类吧?
- Java中的的引用类型有哪几种?
- ThreadLocal应用场景都有哪些?
- 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是以耗费内存为代价的。
-
在多层嵌套的方法中替代参数的显式传递
也就是在上下文传递信息,线程内的所有方法都能获取到,避免一些参数传递。
-
多数据源动态切换
网友评论