重点
TheadLocal:用于线程间变量隔离,仅能在当前线程内访问,无法使用ThreadLocal进行父子线程变量传递。
ThreadLocalMap定义在ThreadLocal中的内部类,Thread中存在一个ThreadLocalMap属性
ThreadLocal内部维护的ThreadLocalMap是与当前线程绑定(ThreadLocalMap是当前线程的一个属性)
添加数据时,map的key为当前ThreadLocal实例;一个线程可中可进行多个ThreadLocal创建
当元素冲突时,采用开放寻址方式,而非链表方式。
一、应用场景
在使用多线程情况下,程序在处理用户请求的时候,通常后端服务器是有一个线程池,对每一个请求就分配一个线程来处理,那为了防止多线程并发处理请求的时,发生线程数据安全问题(串数据),利用ThreadLocal实现线程间变量隔离。
使用案例
//存放用户信息的ThreadLocal,一般使用Util类进行封装
private static final ThreadLocal<UserInfo> userInfoThreadLocal = new ThreadLocal<>();
public Response handleRequest(UserInfo userInfo) {
Response response = new Response();
try {
// 1.用户信息set到线程局部变量中
userInfoThreadLocal.set(userInfo);
doHandle();
} finally {
// 3.使用完移除掉
userInfoThreadLocal.remove();
}
return response;
}
//业务逻辑处理
private void doHandle () {
// 2.实际用的时候取出来
UserInfo userInfo = userInfoThreadLocal.get();
//处理业务操作....
}
二、实现细节
实现总结
ThreadLocal
单个线程可以进行多次执行new ThreadLocal()操作,每次调用ThreadLocal的set()方法或者setInitialValue()方法,都会在内部的ThreadLocalMap中添加一条数据(类似于执行了一次put操作)
key:当前ThreadLocal对象
value:需要存储的线程局部变量
ThreadLocalMap【Thread的一个成员变量】
当发生hash冲突时,采用开放地址法进行解决冲突
ThreadLocalMap底层使用Entry数组,初始大小16。其中Entry继承WeakReference,仅将Entry的key设置为弱类型**。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//调用父类构造方法,将key设置为弱引用类型
super(k);
value = v;
}
}
public WeakReference(T referent) {
super(referent);
}
key设置为弱引用原因
为了尽最避免内存泄漏,但是如果仅仅这样,还是无法避免内存泄漏。ThreadLocalMap是根据key是否为null来判断是否清理Entry???(没找到依据)。
系统一般使用线程池将线程进行复用,线程生命周期很长。根据GC root搜索方式,进行垃圾回收,Thread—>ThreadLocalMap-Entry会一直存在。
**
ThreadLocal的设计者认为只要ThreadLocal 所在的作用域结束了工作被清理了(但有时ThreadLocal并不会顺利清理),GC回收的时候就会把key引用对象回收,key置为null,ThreadLocal会尽力保证Entry清理掉来最大可能避免内存泄漏。
ThreadLocal 所在的作用域结束,并没有清理时,TheadLocal会一直被强引用关联
**
正常初始化时引用结构
弱引用
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
具体分析
实例化方式1
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
实例化方式2
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
//在ThreadLocal中设置变量
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
//获取当前线程关联的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//初始化ThreadLocalMap
createMap(t, value);
return value;
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
三、存在问题与避免
内存泄漏
Entry 继承了WeakReference类,Entry 中的 key 是WeakReference类型的(Value为强引用),在Java 中当对象只被 WeakReference 引用,没有其他对象引用时,被WeakReference 引用的对象发生GC 时会直接被回收掉。
解决方案
手动调用remove函数
class Threadlocal {
public void remove() {
//获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); //this就是ThreadLocal对象,移除,方法在下面
}
}
class ThreadlocalMap {
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;
}
}
}
}
//清理数组元素
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//把staleSlot的value置为空,然后数组元素置为空
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; //元素个数-1
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//k 为null代表引用对象被GC回收掉了
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//因为元素个数减少了,就把后面的元素重新hash
int h = k.threadLocalHashCode & (len - 1);
//hash地址不相等,就代表这个元素之前发生过hash冲突(本来应该放在这没放在这),
//现在因为有元素被移除了,很有可能原来冲突的位置空出来了,重试一次
if (h != i) {
tab[i] = null;
//继续采用链地址法存放元素
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}