文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、ThreadLocal的总体理解
注意:ThreadLocal虽然是本地线程,但是存储的对象依然是放在堆中的。
理解 | 描述 |
---|---|
创建 | ThreadLocal<隔离类> threadlocal = new ThreadLocal<>(隔离类); |
作用 | ThreadLocal为每个线程都提供了局部变量,每个线程都可以通过set/get对各自局部变量进行操作,从而实现线程的数据隔离。相比较加锁保证线程安全,ThreadLocal采用了空间换时间的思想,并发性能更好。 |
应用场景 | ① Spring中多线程下的singleton模式的有状态Bean的线程安全问题; ② Spring通过ThreadLocal实现了事务隔离。具体的,Spring采用ThreadLocal的方式,来保证多线程环境下,每个单线程从始至终的数据库操作使用的是一个数据库连接(key是ThreadLocal,value是connection)。同时,采用这种方式可以使Service层事务时无需感知和管理connection对象。通过Spring传播级别,巧妙地管理多个事务配置之间的切换、挂起和恢复。 ③ 项目中的DateUtils工具类,主要是通过SimpleDataFormat对日期进行格式化。在多线程环境下,由于SimpleDateFormat不是线程安全的。这导致项目上线后,用户的日期可能不对。因此,可以用ThreadLocal让每个线程装载着自己的SimpleDateFormat对象,以能够线程安全的实现格式化时间的目的。 ④ Session和Cookies的数据隔离也是由ThreadLocal实现的。项目中将用户的登录状态放入ThreadLocal中,管理不同用户的登录信息,从而实现了不同用户之间的用户数据线程隔离问题。 |
底层原理 | ① ThreadLocal类只是一个外壳,真正存储数据的是ThreadLocal内部的ThreadLocalMap结构。ThreadLocalMap的引用是定义在Thread类中的(threadLocals)。因此,实际上ThreadLocal本身并不存储值,它只是作为key来让线程从ThreadLocalMap获取对应的Value。 ② 因此,每个线程创建ThreadLocal时,实际上数据是存储在自己线程Thread类内部的threadLocals变量中的,其它线程无法拿到,从而实现了数据隔离。 |
数据结构 | ③ ThreadLocalMap类虽然名字中带Map,但是它并没有实现Map接口。它底层的数据结构是一个继承了弱引用WeakReference 的Entry,key是ThreadLocal,value是隔离的数据。② Entry的底层是数组,并没有用链表。存放数据时,同样是根据key计算hashcode,然后对长度取模获得存放索引。如果存放的位置上有元素,那么就比较key值;如果key值相同,则比较key值,若key值相同则更新。若key值不相同,则循环查找下一个为空的存储位置,以此解决哈希冲突。但是在set/get并发高的时候,效率较差。 |
内存泄漏 | ① 原因: 由于ThreadLocal底层存放数据的ThreadLocalMap中的Entry对象继承了弱引用。当ThreadLocal被回收&&线程被复用&&线程复用后不再调用ThreadLocal的set/get/remove方法,可能会发生内存泄露。 ② 解决办法: 使用完ThreadLocal后,主动remove掉。 |
二、ThreadLocal的set/get底层源码
总的来说,就是获取当线程Thread类,从中获取到ThreadLocalMap。然后以当前的ThreadLocal为key,去获取对应的value。
public void set(T value) {
Thread t = Thread.currentThread();// 获取当前线程
ThreadLocalMap map = getMap(t);// 获取从当前线程t中,获取ThreadLocalMap对象
if (map != null) // 校验对象是否为空
//this为当前ThreadLocal
map.set(this, value); // 不为空set
else
createMap(t, value); // 为空创建一个map对象
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class Thread implements Runnable {
……
//真正存储ThreadLocal值的地方
ThreadLocal.ThreadLocalMap threadLocals = null;
//多线程间共享ThreadLocal的实现方式,主线程中new一个final inheritableThreadLocals,然后多线程间共享
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
ThreadLocal为什么不把Thread直接作为key?
- ① 因为
一个线程拥有多个变量
,如果把线程做为key,那么我们还需要设置额外的标识来判断此时想取哪个value。 - ② 如果使用ThreadLocal作为key,那么底层的ThreadLocalMap就不能扩张了,相当于一直只有一个Entey<Thread,value>;
三、ThreadLocalMap–底层数据结构
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的方式
使用InheritableThreadLocal
可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal
的实例,然后在子线程中得到这个InheritableThreadLocal
实例设置的值。
public class Thread implements Runnable {
……
//真正存储ThreadLocal值的地方
ThreadLocal.ThreadLocalMap threadLocals = null;
//多线程间共享ThreadLocal的实现方式,主线程中new一个final inheritableThreadLocals,然后多线程间共享
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
五、ThreadLocal内存泄漏问题
1、内存泄漏的条件
2、解决方法
用完ThreadLocal就要主动remove掉;