ThreadLocal
参考资料:https://www.cnblogs.com/fsmly/p/11020641.html
JDK中java.util.lang包下的类。
ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量,并且不会和其它线程的局部变量产生冲突,实现线程间的数据隔离。
ThreadLocal的应用场景:
- 保存线程上下文信息
- 实现线程间的数据隔离
- 数据库连接
ThreadLocal简介
ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
ThreadLocal简单使用
package JUC.Guigu.threadLocal;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo1 {
// 创建ThreadLocal变量
static ThreadLocal<String> lovalVar = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
// 设置当前线程中本地内存中本地变量的值
lovalVar.set("localVar1");
System.out.println(Thread.currentThread().getName() + ":" + lovalVar.get());
// 清除本地内存中的本地变量
lovalVar.remove();
System.out.println("localVar1 has removed");
System.out.println("after remove : " + lovalVar.get());
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
lovalVar.set("localVar2");
System.out.println(Thread.currentThread().getName() + ":" + lovalVar.get());
lovalVar.remove();
System.out.println("after remove : " + lovalVar.get());
}, "t2").start();
}
}
// t1:localVar1
// localVar1 has removed
// after remove : null
// t2:localVar2
// after remove : null
/*
每个线程Thread实例对象都维护了自己的threadLocals成员变量,该变量的类型为ThereaLocal类的内部类ThreadLocalMap类型。
通过调用ThreadLocal类(工具类)的set和get方法,能够对当前线程的成员变量threadLocals中添加map映射,key为ThreadLocal类的引用,value为自己设置的值。
*/
ThreadLocal原理
// 1. Thread有两个属性,threadLocals 和 inheritableThreadLocals
public class Thread implements Runnable {
// 此线程有关的ThreadLocal值。此 Map 由 ThreadLocal类来维护。
// 每个线程的本地变量存放在自己的本地内存变量threadLocals中,线程不消亡,本地变量就会一直存在。
ThreadLocal.ThreadLocalMap threadLocals = null;
// 与此线程有关的 InheritableThreadLocal 值。 该Map由 InheritableThreadLocal 类维护。
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
// ==============================================================================
// 2. ThreadLocal有一个内部类,ThreadLocalMap
public class ThreadLocal<T> {
// 内部类
static class ThreadLocalMap {
}
// ThreadLocal 有 set get 方法。只有第一次调用的时候,才回去初始化内部类
// set方法
public void set(T value) {
// 获取当前线程(调用者线程)
Thread t = Thread.currentThread();
// 以当前线程作为key值,去查找对应的线程变量,找到对应的map。
ThreadLocalMap map = getMap(t);
// 如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
else
// 如果map为null,说明首次添加,需要首先创建出对应的map
createMap(t, value);
}
// get方法
public T get() {
// 获取调用者线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
// 不为null,则可在map中找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 为null,创建map对象,并返回初始化值。
return setInitialValue();
}
// 返回Thread的threadLocal属性值
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// 创建threadLocals, 同时将要添加的本地变量值添加到了threadLocals中。
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private T setInitialValue() {
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 当前线程作为key,查找对应的本地变量值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 为null,首次添加,创建对应的map
createMap(t, value);
return value;
}
// 将threadLocal中删除不用的本地变量
public void remove() {
// 获取当前线程的threadLocals变量
ThreadLocalMap m = getMap(Thread.currentThread());
// map不为null,移除当前线程中指定ThreadLocal实例的本地变量
if (m != null)
m.remove(this);
}
}
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。
在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能。
ThreadLocal内存泄漏问题
明确几个方法:
1.set:调用ThreadLocal.set方法,添加一个map对象,key为ThreadLocal的弱引用,value为set方法中传入的值。将该map对象赋值给Thread对象的threadLocals成员变量。
2.get: 调用ThreadLocal.get方法,通过当前线程的成员变量threadLocals得到对应的map,并返回map中的value值。
3.remove: 通过当前线程的成员变量threadLocals得到对应的map,删除map。
问题来了:
不同线程实例的成员变量threadLocals中的map对象的键都是ThreadLocal类的弱引用。如果一个线程结束了,还有别的线程存在对ThreadLocal的引用,那么已结束线程的threadLocals中的map对象中,其中key是弱引用会被 GC,但是对应的value还是存在的,不会被GC掉,造成内存泄漏。
总结:
ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLocal依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要在实际使用的时候在使用完毕及时调用remove方法避免内存泄漏。
ThreadLocal使用案例
package JUC.Guigu.threadLocal;
// 参考资料:
// https://blog.csdn.net/qq_40328109/article/details/106494709
public class ThreadLocalDemo2 {
public static void main(String[] args) {
new Thread(() -> {
UserSession u = new UserSession(11, 11);
SessionCache.set(u);
System.out.println(Thread.currentThread().getName() + "\tuserId: " + SessionCache.get().getUserId() + "\tpassword:" + SessionCache.get().getPassword());
SessionCache.remove();
}, "线程1").start();
new Thread(() -> {
UserSession u = new UserSession(12, 12);
SessionCache.set(u);
System.out.println(Thread.currentThread().getName() + "\tuserId: " + SessionCache.get().getUserId() + "\tpassword:" + SessionCache.get().getPassword());
SessionCache.remove();
}, "线程2").start();
}
}
class UserSession {
private int userId;
private int password;
public UserSession(int userId, int password) {
this.userId = userId;
this.password = password;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getPassword() {
return password;
}
public void setPassword(int password) {
this.password = password;
}
}
// 工具类 相当于ThreadLocal
class SessionCache {
// 线程变量 创建ThreadLocal变量
private static final ThreadLocal<UserSession> session = new ThreadLocal<UserSession>() {
public UserSession initiaValue() {
return null;
}
};
public static void set(UserSession s) {
session.set(s);
}
public static UserSession get() {
return session.get();
}
public static void remove() {
session.remove();
}
}