ThreadLocal
我们需要知道关于ThreadLocal的几个问题先提出:
-
ThreadLocal是什么?(介绍)
-
有什么用?(作用)
-
我该怎么使用它(使用)
-
它类的底层是怎样的?(底层)
-
它会有什么问题呢?(缺点)
介绍
直接在官网找到对该类的介绍:
该类提供线程局部变量。这些变量与其正常对应变量的不同之处在于,访问一个变量(通过其 get
或 set
方法)的每个线程都有其自己的、独立初始化的变量副本。 ThreadLocal
实例通常是类中希望将状态与线程关联起来的私有静态字段(例如,用户 ID 或事务 ID)。
ThreadLocal (Java Platform SE 7 )
个人对上述文字的理解:ThreadLocal就是每个线程中的局部变量,每个线程中都要。可以通过set或get方法访问。
作用
我们知道它是单独存在每个线程中的,所以我们可以推测出一下的作用:
线程封闭性(Thread Confinement):
ThreadLocal
可以用于将某个对象与当前线程关联起来,使得该对象只能被当前线程访问,而其他线程无法访问。这样的数据隔离能够提高多线程程序的安全性。线程上下文共享: 在某些情况下,线程之间需要共享一些数据,但这些数据对于其他线程是不可见的。
ThreadLocal
可以用于在线程内部传递数据,而不需要将数据暴露给其他线程。避免传递参数: 使用
ThreadLocal
可以避免在方法调用链中频繁传递相同的参数。例如,在 Web 应用中,可以将当前用户的信息存储在ThreadLocal
中,这样在整个请求处理过程中都可以方便地访问到,而不需要在每个方法中都传递用户对象。简化线程安全问题: 使用
ThreadLocal
可以避免一些线程安全问题,因为每个线程都拥有自己的变量副本,不需要考虑线程间的竞争和同步。
使用
比如说,在一个系统,用来存储当前线程登录的用户信息。把它的方法封装成一个工具类来使用。
使用流程:
-
创建一个ThreadLocal对象
-
设置值
-
需要的时候,把值取出
-
退出系统的时候,移除变量值
//这里是将ThreadLocal封装成了一个工具类
public class UserHolder {
//创建一个ThreadLocal对象
//注意,这里的static修饰其实是非常重要的,下面说的它的问题中会提到
//这里的static就相当是将ThreadLocal类进行了强引用,key和value不会被垃圾回收
//所以我们要注意,在线程不使用的时候,调用remove方法,将value移除,不然就容易出现当前线程读取到之前使用线程中存储的数据了
private static final ThreadLocal<User> tl = new ThreadLocal<>();
//设置值
public static void saveUser(User user){
tl.set(user);
}
//取出值
public static User getUser(){
return tl.get();
}
//移除值
public static void removeUser(){
tl.remove();
}
}
底层
直接先来看ThreadLocal类中的源码:
1、先看get方法:
public T get() {
// 1. 获取当前线程
Thread t = Thread.currentThread();
// 2. 从当前线程获取线程局部变量
// 可以看出,其实存储线程局部变量的是一个ThreadLocalMap类
// 这个ThreadLocalMap类是在ThreadLocal类中的一个内部静态类
// 这个ThreadLocalMap同时也是一个哈希表
ThreadLocalMap map = getMap(t);
if (map != null) {
// 3. 从ThreadLocalMap中获取与当前ThreadLocal对象相关联的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 4. 如果当前线程没有与ThreadLocal对象相关联的值,则调用setInitialValue()方法进行初始化
return setInitialValue();
}
这里是getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这里是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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
createMap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这里是ThreadLocalMap构造函数:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//......
}
总结一下:
每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。
缺点/问题
对于ThreadLocal最经常的问题就是:ThreadLocal 内存泄露问题是怎么导致的?
我们先要了解一个知识就是:
ThreadLocal的key 是弱引用,value是强引用
如果ThreadLocal没有被外部强引用的时候,在垃圾回收的时候,key会被清理,value不会。
如果在ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 回收,这个时候就可能会产生内存泄露。
弱引用介绍:
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
解决方案:通过ThreadLocal中的remove()方法,及时将value释放。
然后在这个最后,肯定有人提出,
为什么把ThreadLocal的key设置成弱引用呢?还会内存泄露吗?
-
如果我们就当ThreadLocal 的key是强引用,当我们不再使用这个对象的时候,这个 ThreadLocalMap的引用仍然一直在,垃圾回收器不会回收这个对象,如果不去进行手动移除,同样会导致内存泄露。
-
所以,我们知道,如果ThreadLocal的key 如果是强引用,会同样导致和value一样的内存泄露。
-
所以将ThreadLocal的key设置为弱引用的目的是避免因为
ThreadLocal
使用不当而导致的内存泄漏问题。
小结
ThreadLocal类提供线程局部变量。
作用:实现线程上下文共享
使用:创建 -> 设置值 -> 取出值 ->移除值
每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。
问题:ThreadLocal的key 是弱引用,value是强引用。需要注意当ThreadLocal不被引用的时候,使用remove将value移除