一、ThreadLocal原理
1.ThreadLocal:线程本地变量,对于同一个ThreadLocal,每个线程通过get/set/remove方法操作时只会影响自身线程的数据。
2.ThreadLocalMap:key为ThreadLocal对象的弱引用,value为要存储的变量值。类似HashMap的数据结构(但它没有实现map接口,更不是hashmap的数据结构)。
3.ThreadLocalMap是ThreadLocal的静态内部类;Thread类有一个成员变量:ThreadLocal.ThreadLocalMap threadLocals = null; 在对ThreadLocal做set/get的时候,会通过:Thread t = Thread.currentThread(); 先拿到当前线程thread对象, 然后通过ThreadLocalMap map = getMap(t); 拿到这个Thread对象的成员变量ThreadLocalMap,再做set/get。
4.实现:(1) ThreadLocal仅仅是个变量访问的入口,真正的变量副本绑定到当前Thread上的成员变量ThreadLocalMap里(这个ThreadLocalMap持有对象的引用); (2) ThreadLocalMap以当前的threadLocal对象为key,以真正的存储对象为value。get()方法时通过threadLocal实例就可以找到绑定在当前线程上的副本对象。
5.ThreadLocal+ThreadLocalMap设计的目的:保证当前线程结束时,相关对象可以立即被回收(弱引用)。
//ThreadLocal类的静态内部类ThreadLocalMap:
static class ThreadLocalMap {
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table; // 存储数据的数组
private static final int INITIAL_CAPACITY = 16; //map的默认大小
// 存数据的结构: Entry对象, key是ThreadLocal对象的弱引用,value是那个值
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // The value associated with this ThreadLocal.
Entry(ThreadLocal<?> k, Object v) { //构造方法
super(k);
value = v;
}
}
}
// ThreadLocal的set:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 拿到当前线程的map
if (map != null)
map.set(this, value); // 放KV: K是当前ThreadLocal对象的弱引用,V是value
else
createMap(t, value);
}
// ThreadLocal的get:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 拿到当前线程的map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 拿到当前TreadLocale对象的弱引用对应的Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
6.为什么要用弱引用:减少内存泄露(外部强引用消失时,Entry的key会变成null;否则该ThreadLocal对象一直不会被清理除非线程销毁了,线程池中长时间不销毁,内存泄露的问题会累积)。真正避免内存泄露:及时调用ThreadLocal的remove方法/及时销毁线程
7.ThreadLocal和其他同步机制(syn/lock):
同:解决多线程中并发访问的冲突;
异: 通过控制线程对共享资源的访问时间解决冲突 & 从空间上隔离数据
项目中的使用: 设置动态数据源:部分接口只有查询且可以在从库进行查询时(查agent基础数据、配置的快捷话术等),通过ThreadLocal修改访问的DB配置,然后在退出的时候还原配置,达到只有这个线程访问从库的效果。
数据源类型放在ThreadLocal中;
继承AbstractRoutingDataSource抽象类,重写determineCurrentLookupKey()方法,在这里返回ThreadLocal里面的key。
determineCurrentLookupKey()无法在事务中运行(动态切换失效),因为事务需要连接,并且该方法的目的是确定使用哪个DataSource来获取连接。