背景: 今天在开发项目的时候碰到一个线程安全的问题。具体的情况是这样的,服务是基于dubbo对外提供服务的。所有的请求就是java服务多线程处理的。我写的一个类里面有多个方法需要重复的查询DB来获取数据,使用希望定义一个全局变量来保存,只需要查询一次即可。但是由于是一个静态变量在多线程环境下面是不安全的。所以需要一个办法来让静态变量在线程与线程直接互相独立,互不干扰。后经同事的提醒,可以用ThreadLocal来解决,所以先对这个类进行了一次了解。
探索过程
首先要了解java变量作用域的问题。
静态变量: 线程非安全。静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。
实例变量: 单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。
局部变量: 线程安全。每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。
这里我使用的是静态变量,多线程下面变量的内存区域是共享的。这就意味着当一个线程给这个变量负责了a下面的逻辑需要这个变量为a才能保证逻辑正确,但是此时另外一个线程将这个变量改成了b。咔~~,程序逻辑就错了。而且这种还不是语法错,还是偶然性的,出问题的时候可能就需要花很多时间来排查问题。
那ThreadLocal是怎么避免这个问题呢.
原理大概是一个线程的map,可以理解为Map<Thread, Map<K, V>>这样的结果,Thread就是线程号了,下面的map就是我们使用存储的值。
详细内容看这里深入剖析ThreadLocal实现原理以及内存泄漏问题
在这篇文章里面同时也引入了另外一个问题,ThreadLocal内存泄露的问题。
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
顺便去喵了一眼这个博主的博客,才知道他还没有毕业。看看他在大学里面干的事情,再回想自己当年大学里面是浪费了多少时间。多么无知。
最后带一个使用方法吧,看这些博文里面都没有写怎么用
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
threadLocal.set(100L);
threadLocal.get();
threadLocal.remove();