在做项目时发现项目中一般都会把用户信息存入ThreadLocal中,方便后续使用用户信息。但是ThreadLocal的原理是什么呢?这里结合网上的资料记录一下我自己的理解。
ThreadLocal是什么?
网上有的说法是ThreadLocal是线程本地变量,如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝
,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
但是我觉得这段话说的不够清楚,甚至有歧义,至少我初看时是理解错了。
实际上这个ThreadLocal就是一个类,用来操作ThreadLocalMap的一个工具类(后面会解释为什么这么说),那这个ThreadLocalMap又是什么呢?
每个Thread线程下面都有一个成员变量threadLocals,可以看到数据类型就是ThreadLocalMap。
所以并不是每个线程都有一个ThreadLocal,而是每个线程都有一个ThreadLocalMap,而ThreadLocalMap根据这个名字就应该知道具体结构是一个map结构,如下图所示。
如果有多个用户,即多个线程,情况就是
可以看到对于ThreadLocalMap每个entry的key就是ThreadLocal变量本身的引用,而value为不同线程下的存入的数据。
并且,可以看出来,不同的线程中有不同的ThreadLocalMap,但是entry中key指向ThreadLocal是同一个,也就是说ThreadLocal是被不同的线程共享的,或许这就是文章开头那段概念中“访问这个变量的每个线程都会有这个变量的一个本地拷贝”
的意思?
ThreadLocal如何保证线程隔离?
线程隔离就是指存在这个线程的数据不能被其它线程改变。
以存取用户信息为例子。
- 创建一个ThreadLocal变量
private static final ThreadLocal<User> threadLocal = new ThreadLocal<>();
- 存数据。
threadLocal.set(user);
set的具体实现其实是先得到此线程的ThreadLocalMap,然后以threadLocal自身引用为key,user为value,存进ThreadLocalMap。代码如下。
- 取数据
threadLocal.get();
get的具体实现也是先获得当前线程的ThreadLocalMap,然后以threadLocal自身引用为key,去ThreadLocalMap中找对应的value。(谁调用的get()谁就是key)
- 因为不同线程拥有不同的ThreadLocalMap,所以不同线程的存取数据操作都是在不同的空间的进行的,互不影响。达到线程隔离的效果。其实也就是每个线程开辟了一个自己的独立空间(ThreadLocalMap)存数据,虽然开启空间的钥匙是一样的(ThreadLocal),但是你没办法拿这个钥匙开启我的空间,因为你根本看不到我的空间,没有访问我的空间的机会。
- 可以看出来,threadLocal.set(),threadLocal.get()其实都在对ThreadLocalMap进行读写,所以前面我才说ThreadLocal就像是一个用来操作ThreadLocalMap的工具类。
ThreadLocal内存泄漏问题
这里要注意的一点是,ThreadLocalMap中entry的key,也就是ThreadLocal变量本身的引用是一个弱引用。
而弱引用只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存。因此ThreadLocalMap中的key很容易被回收了,key为null,而这时候整个ThreadLocalMap还在,因为ThreadLocalMap生命周期是和Thread一样的。key没了,value还在,这就造成了内存泄露。
而解决这个问题的办法就是使用完ThreadLocal后,手动调用remove()方法释放内存空间。