ThreadLocal在平时开发中是用的比较多的一个类,主要用于存储线程的数据。下面我对ThreadLocal进行一下总结;
dreamcatcher-cx大佬对ThreadLocal的设计总结,写的比较深刻,很有帮助。
多数据源切换,记录当前线程访问的数据源
spring框架事务管理,用于存放事务数据;
springsecurity安全框架,用于存储用户登录信息;
除了以上还有很多,不限于以上。
通过Entry数组进行存储的;
Entry继承Reference类,是弱关联的类,当ThreadLocal的实例为空时,GC会快速收回;一般用于处理数据量较大且维持时间较短的业务。
Entry节点存储的是ThreadLocal对象和对象值
//是通过Entry数组进行存储的
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
存放ThreadLocal<?>的是弱关联的容器,GC会等存储满的时候处理;
存放的过程中,会对key判断,调整数组位置;
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);//获取存放的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();//获取存入的ThreadLock
if (k == key) {//判断是否一致,如果一致,替换值并返回
e.value = value;
return;
}
if (k == null) {//如果i节点获取到的ThreadLocal为空的话
/**
* 1.从i 的向前循环查找tab[i]节点的ThreadLocal不为空的位置slotToExpunge;
* 2.从i以下节点循环遍历,会有两种情况;
* 3.第一种情况:找到对应key相等的节点,和当前遍历的节点位置进行交换;如果slotToExpunge和i节点相同,则进行rehash
* 4.第二种情况:key为空,并且slotToExpunge和遍历的位置相等的情况下,slotToExpunge == 遍历的位置;
* 5.根据传入的i和slotToExpunge是否相等,进行数组的节点的清除和rehash操作
*/
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);//新增信的节点
int sz = ++size;
/**
* 判断是不是要进行扩容
* Entry数组的扩容是2倍扩容的;
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
/**
* 从i位置往后逐个遍历查找如果找到了就返回,没找到返回null;
*/
return getEntryAfterMiss(key, i, e);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
首次存储,新建一个ThreadLockMap,以当前ThreaLock为key;
如果线程的存储区域已经初始化过,则更新存储区域中的数据;
public void set(T value) {
Thread t = Thread.currentThread();// 获取当前线程
ThreadLocalMap map = getMap(t);//获取存储数据的MAP
if (map != null)//判断存储数据的map是否为空,如果为空则创建map,如果不为空,则存入数据
map.set(this, value);
else
createMap(t, value);//如果没有就创建一个,当前Thread
}
//获取当前线程的值存储区域
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //线程的成员变量
}
//给当前线程创建一个新的ThreadLocalMap,并存储以当前ThreaLocal实例为key的键值对。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如果在访问之前没有存储任何数据,则对ThreadLockMap进行初始化,绑定当前线程。并进行初始值的创建,和当前ThreadLock实例进行绑定,kv存储
如果获取到之前存储的值,则进行返回存入的值;
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获取当前线程的存储map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//获取当前的ThreadLock的节点,具体上面有介绍
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//如果不存在则进行初始化
}
//进行一些初始化操作
private T setInitialValue() {
T value = initialValue();//ThreaLock提供的一个可继承的方法,进行初始化操作
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获取当前线程的
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)//在存储的map不为空的情况下,移除当前ThreadLock实例
m.remove(this);
}
1. ThreadLocalMap存储的key值为当前的ThreadLock实例,所以一般要求ThreadLocal是单例的
2. 单个线程可以有多个ThreadLocal实例,存储数据。
原理:相当给当前线程的创建独有数据域,方便当前线程访问。这样避免了多线程环境中,访问当前线程锁的相关问题
之所以使用ThreadLocalMap进行存储,而不用HashMap,上面源码分析说了,源于Reference类的特性。
弱应用,当对象无用的时候,会快速被GC回收。符合线程的特点,一般一个线程时间不会很长,当伴随大量数据时能快速回收
当前线程结束的时候,要通过调用ThreadLocal的remove方法,即使将ThreadLocalMap中的对应实例key设置为空,方便GC快速回收
ThreadLocal操作更有优势,下面我具体分析以下
1.简化代码:
试想一下上面的源码,如果直接从Thread中存放数据,
首先,要用Thread.currentThread()获取当前线程,
然后,再通过当前线程thread获取存储的Map
再根据key获取对那个的操作
从上面的源码可以看出,这些操作细节,都已经在ThreadLocal类中进行了隐藏;
1.避免每次使用时的重复代码;
2.而使用ThreadLocal实例获取数据,相当于拿key直接获取数据,隐藏了获取细节
2.限制和统一管理存储值
1. 内部的Entry数组,进行了泛型约束,避免了直接在Thread类中Map操作key的不规范性;
2. 避免了开发人员直接操作当前线程,而使用“变量副本”进行操作