简单概述
ThreadLocal叫做线程变量 ,一句话描述,他就是属于当前线程的独有变量。
我们使用ThreadLocal的时候,设置的是同样的Key,但是他的Value在每个线程中是不同的。每一个使用这个变量的线程都会初始化一个完全独立的实例副本。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例 且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
数据结构
可以看到每个Thread都有一个ThreadLocalMap ,既然叫map,就有key和value,这里面的key就是一个ThreadLocal的弱引用 ,value就是放入的Object值。每个线程往ThreadLocal里面放值的时候都会存到ThreadLocalMap里面,读也是以TheadLocal作为引用去map里面读。
总结,一个线程里面有个map,可以放很多threadlocal,threadlocal本身不是map结构,他只是这个map中的一个key。
简单实用
csharp
public class ThreadLocalUtils {
private final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void set(String s) {
threadLocal.set(s);
}
public static void get() {
System.out.println(threadLocal.get());
}
public static void remove() {
threadLocal.remove();
}
}
public class Main {
public static void main(String[] args) {
ThreadLocalUtils.set("hello");
ThreadLocalUtils.get();
ThreadLocalUtils.remove();
ThreadLocalUtils.get();
}
}
输出
源码分析
scss
public void set(T value) {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
//则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 初始化thradLocalMap 并赋值
createMap(t, value);
}
获得当前线程,如果当前ThreadLocalMap不为空,那就赋值,否则初始ThreadLocalMap并且赋值。
scala
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内部有个Entry来保存数据,这个Entry继承弱引用。在Entry内部使用ThreadLocal作为key
ini
//这个是threadlocal 的内部方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocalMap 构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
初始化方法
scss
public T get() {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3、如果map数据不为空,
if (map != null) {
//3.1、获取threalLocalMap中存储的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
get方法也比较简单,就是两种情况,一种get的key map中存在就直接返回得到的result,如果key不存在那就初始化这个threadLocalMap,并且以传入的threadLocal作为key,值为null作为第一个Entry。
csharp
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
删除方法就是在当前线程中取出map,map删除掉这个Entry。
引出内存泄漏问题
实际上ThreadLocal中的Key是ThreadLocal的弱引用,也就是在GC的时候会被清理掉,这样ThreadLocalMap使用这个ThreadLocal的key也会被清理掉,但是value是强引用,不会被清理。这样可能会出现key为null的value
弱引用:发生GC的时候就会回收掉弱引用对象
强引用:不会被GC回收
这个value和线程的生命周期同步,但是在线程池环境下,线程生命周期可能和JVM同步,因此会发生内存泄漏,如果不断取线程写入大对象,不及时remove,久而久之所泄漏的内存空间就会持续膨胀。下面引出四个问题
- 为什么key不能是强引用? 强引用的key会导致内存泄漏问题,因为ThreadLocalMap中的key通常是ThreadLocal实例,如果使用强引用,即使线程不再需要该ThreadLocal实例,由于强引用的存在,该实例也无法被垃圾回收,从而造成内存泄漏。
- 为什么key选择使用弱引用? 使用弱引用的key可以避免内存泄漏问题。当线程不再需要该ThreadLocal实例时,如果该实例只被ThreadLocalMap中的弱引用key引用,而没有其他强引用指向它,垃圾回收器就可以自动回收这部分内存,释放对应的ThreadLocal实例。
- 为什么value使用强引用? value使用强引用是为了确保在需要时能够正常访问和使用对应的值。如果value使用弱引用,当线程需要访问该值时,如果此时该值被垃圾回收器回收了,就无法正常获取到预期的值,破坏了ThreadLocal的预期行为。
- 为什么value不使用弱引用? 如果value使用弱引用,可能会导致在线程仍需要该值时,值被过早回收的情况发生,这不符合ThreadLocal的设计初衷,即确保每个线程都能获取到自己设置的值,而不受垃圾回收的影响。因此,value通常使用强引用,以保证线程可以正常地访问和操作它们。