首先我们模拟个场景,看看ThreadLocal的效果是怎么样的。
有如下代码,有一个学生类,定义一个全局变量ThreadLocal,然后再main方法分别启动一个线程去操作这个ThreadLocal,第一个线程set内容,第二个线程get,看看是否能获取到:
/**
* @description: ThreadLocal与弱引用
* @author:weirx
* @date:2021/5/20 10:07
* @version:3.0
*/
public class ThreadLocalAndWeakReference {
static class Student {
private String name;
public Student(String name) {
this.name = name;
}
}
static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
threadLocal.set(new Student("zhangsan"));
}).start();
Thread.sleep(1000);
new Thread(() -> {
System.out.println(threadLocal.get());
}).start();
}
}
结果:
null
相信了解ThreadLocal的同学都知道这是必然的。
接下来我们通过分析其set方法,看看是如何做到的.
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//根据当前线程获取map
ThreadLocalMap map = getMap(t);
if (map != null)
//将this(当前ThreadLocal)设置为key,将value设置为map的value。
map.set(this, value);
else
createMap(t, value);
}
getMap是个什么鬼?
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
我们通过以上源码,发现通过t线程获取其threadlocals。每个线程有自己的ThreadLocal,到这里就能明白为什么多个线程使用一个ThreadLocal不会相互产生影响。
线程的ThreadLocal接下来我们继续看看ThreadLocal是如何实现key,value的缓存的,继续跟踪ThreadLocalMap的set方法:
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();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//根据传过来的k、v new了一个Entry
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
通过上述代码我们主要关心这个Entry:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们发现这个Entry是集成弱引用的,何为弱引用参考https://www.jianshu.com/p/5d1d5bb86152,通过其构造方法,发现其key仍然是这个ThreadLocal,v仍然是我们的student对象,但是设置key时使用的是supper,也就是弱引用的构造,证明其key是一个弱引用,而其value则是一个强引用。这里记一下,后面会豁然开朗。
我们能够得出结论:
作为key的这个ThreadLocal对象,被两个引用同时引用着;
1)我们开始创建的全局变量threadLocal通过强引用引用着;
2)在Entry当中,被一个弱引用所引用着。
那么我们看看为什么要用弱引用?如果使用强引用会怎么样?
如果两个都是强引用,那么当我们的线程未终止运行,或者threadLocal被设置为null了,那么这个ThreadLocal对象仍然会被一个强引用所引用,将导致不可被回收,从而发生内存泄漏。而弱引用只要发生回收,其生命周期就结束了。
那么即使使用了弱引用就不会发生内存泄漏了吗?
在Entry中,key是ThreadLocal,被弱引用,当它被回收时,这个key就被置为null,其value值就无法被访问到了,也就是前面我们创建的student,如果student在强引用着其他的对象,那么仍然会造成内存泄漏。
最后给出使用的建议:如果我们的ThreadLocal使用完成后,一定要记得手动释放,调用其ThreadLocal的remove方法,防止内存泄漏。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
应用场景:spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。
网友评论