1 什么是ThreadLocal?
多个线程同时访问同一个共享变量会造成并发问题
而ThreadLocal会为每一个线程提供一个独立的变量副本 从而隔离了多个线程对数据的访问冲突
因为每一个线程都拥有自己的变量副本 从而也就解决了多线程的并发访问问题
在很多情况下 ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单更方便 且结果程序拥有更高的并发性
2 在java.lang包下
3 ThreadLocal类直接继承Object类
4 只有一个无参构造方法
ThreadLocal local = new ThreadLocal();
5 该类下常用方法为
4.1 set(T value);
//往里存值
4.2 T = get();
//取值
4.3 remove();
//将当前线程存的局部变量移除
//就算不显示调用这个方法 当线程结束后 这局部变量也会自动被垃圾回收
//但是最好显示调用 不然可能会造成内存泄漏
set源码解析:
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程对象
ThreadLocalMap map = getMap(t);
//通过当前线程对象 t 获取到线程私有的 ThreadLocalMap 对象(ThreadLocalMap是ThreadLocal的静态内部类)
//也就是说每一个线程都有自己的 ThreadLocalMap
if (map != null)
//如果map不为空 往里存值
map.set(this, value);
else
//如果map为空 就先创建map对象
createMap(t, value);
}
//ThreadLocalMap解决hash冲突的方式采用的是线性探测法 如果发生冲突就继续找下一个空位置
get源码分析:
public T get() {
Thread t = Thread.currentThread();
//获取当前线程对象
ThreadLocalMap map = getMap(t);
//通过当前线程对象 t 获取到线程私有的 ThreadLocalMap 对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//通过this(即ThreadLocal对象)找到Entry对象
if (e != null) {
//Entry对象中的值不为空 就将值返回
T result = (T)e.value;
return result;
}
}
return setInitialValue();
//如果找不到就将当前ThreadLocal对象和变量作为键值对存入到ThreadLoclaMap中并返回变量
}
6 可以类比session来了解ThreadLocal存值
1 String JSESSIONID(标识) ——————-> Thread t 当前线程(标识 因为线程是唯一存在的)
2 通过标识找session(可以理解为储物柜) ——————-> 底层通过 t 找 ThreadLocalMap(可以理解为储物柜)
3 通过session.setAttribute(key,value);存值 ——————-> 底层通过 ThreadLocalMap.set(this,value); 存值
key自己定义 在key不同的情况下可以存好多值 注意这里key已经固定了 为this 表示调用这个方法的当前对象(ThreadLocal对象)
因为key是固定的 所以只能存一个信息 后续存入的都会把前面的覆盖
想要存储多个信息 可以将多个信息包装起来在存
7 区分 Thread ThreadLocal ThreadLocalMap
ThreadLocal是Thread的局部变量 一个Thread可以有多个ThreadLocal
ThreadLocalMap是ThreadLocal的静态内部类 用来管理多个ThreadLocal的
ThreadLocalMap底层就是Entry数组 Entry中存的是ThreadLocal的引用和当前ThreadLocal存的值value
8 使用场景
比如在MVC分层中 service业务层通过调用dao层获取到了数据库中的记录
但service层只负责做逻辑判断 只会把判断后的结果返回到controller控制层 那么控制层就不能直接获取数据库中的记录
但我们又想在控制层获取到这些记录 但是若让service层返回一条记录很明显不符合分层架构的要求
所以这时候就用到了ThreadLocal 可以在service层将记录存到ThreadLocal中 然后在控制层获取
但是如何保证获取到的是同一个ThreadLocal对象呢?还需要设计一个类 负责管理ThreadLocal
比如
public class ThreadLocalManager {
//管理不同的ThreadLocal对象 一个人分配一个ThreadLocal
//用每一个人登录的账号作为key 每一个ThreadLocal对象作为值
private static HashMap<String,ThreadLocal> localMap = new HashMap<>();
//通过登录账号获取自己对应的那一个local对象
public static ThreadLocal getThreadLocal(String name){
ThreadLocal local = localMap.get(name);
if(local==null){
local = new ThreadLocal();
localMap.put(name,local);
}
return local;
}
}
9 ThreadLocal会导致内存泄漏
(WeakReference本身是强引用,它内部的才是真正的弱引用,也就是说WeakReference是用来装弱引用的容器而已)
ThreadLocalMap中的Entry继承自WeakReference,使得Entry的key是弱引用
所以ThreadLocal 在没有外部强引用时,发生 GC 时会被回收
那么ThreadLocalMap中就会存在key 为null 的 Entry 对象
而如果创建当前线程一直运行的话,那么这个Entry 对象中的 value 就可能一直得不到回收,那么就导致了内存泄漏
(Entry 被 ThreadLocalMap 对象引用,ThreadLocalMap 对象又被 Thread 对象所引用)
10 如何避免内存泄漏
在使用完 ThreadLocal 后,手动调用 remove 方法即可
(其实也不用太过在意内存泄露的问题,因为ThreadLocalMap在内部set或者get的时候都会清理掉key为null的Entry)