先来看看官方的定义。
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
此类提供线程局部变量,这些变量与普通变量不同,每个访问他们的线程都有自己独立初始化的变量副本。ThreadLocal实例通常是用private static修饰的字段,希望将状态和线程关联(比如用户id,事物id)。
这里很明确的一点:每个线程独立拥有自己的线程局部变量,因此是线程安全的。那么线程局部变量是什么?
应用场景:
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
threadLocal1.set("My name is threadLocal1");
System.out.println("thread1 name:" + threadLocal1.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
System.out.println("thread2 name:" + threadLocal1.get());
});
thread1.start();
thread2.start();
}
}
结果:
thread2 name:null
thread1 name:My name is threadLocal1
为什么thread2线程threadLocal1.get()没有获得My name is threadLocal1呢?
我们通过源码分析其实现原理。
public void set(T value) {
// (1)获得当前线程,即调用线程
Thread t = Thread.currentThread();
// (2)以当前线程为参数,获得当前线程的map,这个就是线程局部变量
ThreadLocalMap map = getMap(t);
if (map != null)
// (3)如果map不为空,则以ThreadLocal实例引用为key保存vaue值
map.set(this, value);
else
// (4)如果map为空,则创建map,并保存value。
createMap(t, value);
}
(2)处的getMap方法:
再点击t.threadLocals得到:
可以看到线程持有一个ThreadLocal.ThreadLocalMap类型的类变量threadLocals,这个变量就是线程局部变量,这就是为什么线程之间互不影响的原因。
(3)处map.put()方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
...
}
这里的key就是示例代码中的变量threadLocal1的 引用。很多文章说是当前线程,其实是不对的。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如果线程的threadLocals为null,则新建一个,并且设置value值。这里的this是变量threadLocal1的 引用
public T get() {
// (1)获得当前线程,即调用线程
Thread t = Thread.currentThread();
// (2)以当前线程为参数,获得当前线程的map,这个就是线程局部变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// (3)获取value值,这里的this还是变量**threadLocal1**的引用
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// (4) 初始化线程的threadLocals变量
return setInitialValue();
}
线程1和线程2各自持有类型为ThreadLocal.ThreadLocalMap的threadLocals变量,key是ThreadLocal的引用,value为线程各自需要保持的值。
一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal,这也是threadLocals是一个类似java.util.Map的原因。
强引用:Java中默认的引用类型,一个对象如果具有强引用那么只要这种引用还存在就不会被GC。
软引用:如果一个对象具有软引用,在JVM发生OOM之前(即内存充足够使用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。
弱引用:如果一个对象只具有弱引用,那么这个对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。
虚引用:所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。对象随时可能被GC掉。
内存泄漏:对象占用着内存,但是因为忘记在哪里,不能被回收,当这种对象越来越多时,内存不过,服务宕机。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocal对象是弱引用而作为Entry的key。当ThreadLocal对象没有强引用,当发生GC时,势必会被GC掉,此时key为null,但是value的强引用还在:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,如果线程不结束,value就不会被GC,造成内存泄漏。线程池使用ThreadLocal很可能造成内存泄漏,特别注意。
参考文章:
https://www.cnblogs.com/fsmly/p/11020641.html#_label4
https://mp.weixin.qq.com/s/rNcarCQI2oVjyJdVR7TaAw
https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc
https://www.cnblogs.com/xzwblog/p/7227509.html
http://www.threadlocal.cn/