ThreadLocal的set,get,remove
在学习数据源切换相关代码的时候,看到了关于ThreadLocal部分的东西,刚开始认为只是个简单的map,没有在意,后来在交流的时候发现颇为重要,专门学习,并记录一下ThreadLocal方面的知识。
首先要明白ThreadLocal是什么,光看名字可以知道是和线程有关的,本地线程?然也不尽然。
ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。
ThreadLocal的确是一个map模型,之所以称之为模型,因为他并不是一个简单的map,本次学习涉及到的是其中最常使用的三个方法:set、get、remove
public class DataSourceHolder {
// 创建一个threadlocal
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
// set方法
public static void setDataSource(String dataSourceName) {
threadLocal.set(dataSourceName);
}
// get方法
public static String getDataSource() {
return threadLocal.get();
}
// remove方法
public static void removeDataSource() {
threadLocal.remove();
}
}
上面是简单创建一个ThreadLocal及其方法的使用,ThreadLocal提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离〜 。
首先我们跟着ThreadLocal的set方法的源码,看一下怎么对这个局部变量进行操作的:
public void set(T value) {
// 既然是ThreadLocal,肯定要有thread,首先获取当前线程对象
Thread t = Thread.currentThread();
// 创建ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果不为空则set一个this!,否则,这个线程就是他的key
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在创建map的时候有一个getMap(t)的方法,让我们看下这个方法的源码
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 点进去查看t.threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
此方法就是简单的获取这个key,是否有对应的value如果有,则覆盖,如果没有则使用createMap创建一个,如果有,则用现在要存储的对象将之前的覆盖
那么让我们看一看这个神奇的ThreadLocalMap是个什么东东,这里截取最关键的一段
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们可以发现ThreadLocalMap是ThreadLocal的一个内部类,用entry类来进行存储,而这个map的key就是当前的ThreadLocal对象
所以我们可以看出,Thread利用了每一个线程作为key,维护这个变量,保证其线程的数据隔离,了解了set方法之后,get方法就非常好理解了,让我们看一下get方法的源码
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();
}
看似很多地方和set差不多,也是先获取t,然后获取自身的ThreadLocalMap,获取之后通过map.getEntry(this)方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。该方法中的 this 即当前访问的 ThreadLocal 对象; 如果获取到的 Entry 不为 null,从 Entry 中取出值即为所需访问的本线程对应的实例。如果获取到的 Entry 为 null,则通过setInitialValue()方法设置该 ThreadLocal 变量在该线程中对应的具体实例的初始值。
而setInitialValue()方法和set差别不大,主要是多了一个初始化和返回
private T setInitialValue() {
// 初始化value
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
remove方法也较为简单啦,获取当前key,然后直接使用remove方法删除,就完事了
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
其实看源码分析ThreadLocal还是比较简单的,但是ThreadLocal的原理主要是什么呢?下面进行一个简单的总结:
1、每个线程维护着一个ThreadLocalMap的引用
2、ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
3、调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
4、调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
5、ThreadLocal本身并不存储值,它只是一个键来让线程从ThreadLocalMap获取值。
那么ThreadLocal是否会引起内存不足的影响呢,当然不会,因为ThreadLocal并不会产生内存开销,在选择键的时候,并非是选择了一个ThreadLocal的实例,实际上是对ThreadLocal实例的弱引用,我们可以看到在ThreadLocalMap中,在用entry存储键值的时候,其内部类Entry继承了WeakReference<ThreadLocal<?>>,而 WeakReference是为了区别直接的对象引用,定义的另外一种引用关系,其标志性特点就是:reference实例不会影响到被应用对象的GC回收行为,只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。所以当threadLocal实例可以被GC回收时,系统可以检测到该threadLocal对应的Entry是否已经过期(根据reference.get() == null来判断,如果为true则表示过期)来自动做一些清除工作,从而防止内存泄露的问题,也不会造成内存不足。
还有一个小坑,是在使用的时候遇到的,在使用动态切换数据源时,在使用完毕之后,一定要调用其remove方法,手动remove,避免造成内存浪费,甚至有可能导致,下一次数据操作的时候,发生找不到数据库,活数据写错地方的情况
以上就是对Threadlocal知识点的总结,和个人观点,欢迎大家评论指正