当前位置: 首页 > 科技观察

再说说ThreadLocal在Java中的作用

时间:2023-03-14 20:59:16 科技观察

在Java中,如果我们用多线程操作变量,就需要加入同步控制机制。原因是多个线程操作一个变量,所以如果每个线程都操作自己的线程变量,那么就不需要加锁,也不需要加同步控制。ThreadLocal就是这个函数。比如在web开发中,我们使用ThreadLocal来保存用户信息,然后在后台传递多个服务,然后每个线程独立获取自己的用户信息;初始化代码也比较简单:publicstaticThreadLocaldateFormatThreadLocal=newThreadLocal(){@OverrideprotectedSimpleDateFormatinitialValue(){returnnewSimpleDateFormat("mm:ss");};使用起来比较简单,通过重载initialValue()方法进行初始化,或者通过set设置,然后get即可使用,整个使用过程和HashMap类似。那么如何神奇的控制不同的线程保存不同的数据,从而实现线程共享,如下:publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")T结果=(T)e.value;返回结果;}}返回setInitialValue();}首先,代码使用Thread.currentThread()获取当前线程id,通过线程id获取对应的ThreadLocalMap。这个getMap实际上是获取Thread的成员变量,如下:ThreadLocalMapgetMap(Threadt){returnt.threadLocals;}这个成员变量定义如下:ThreadLocal.ThreadLocalMapthreadLocals=null;再看这句话:ThreadLocalMap.Entrye=map.getEntry(this);即以这个ThreadLocal的对象为key获取Entry对象,然后获取其value,如果为null则调用setInitialValue()进行初始化,代码如下:privateTsetInitialValue(){T值=初始值();线程t=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);否则createMap(t,value);returnvalue;}这个线程的映射如果存在则不为null,直接更新,返回默认初始化值,即initialValue()的返回值,如果不存在则调用createMap(t,value);创建映射,如下所示:voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}老实说,代码相当复杂。在网上找了一张图,会形成如下结构:【图片来自网络,侵权删】从这张图我们可以看出,注意ThreadLocal是所有线程的map的公共key。还需要注意的是,这张图比较特殊,是内部实现的。hash冲突通过线性检测解决,即如果slot已经被占用,则通过一个函数计算下一个slot。这种方式解决冲突的效率比较低,所以不建议使用过多的ThreadLocal变量。Threadlocal相关数据结构:[图片来自网络,侵权删][图片来自网络,侵权删除]]从上图我们可以看出,Entry继承自弱应用,接下来会回收gc,但只有key是弱引用,value还是强引用。在下一次gc中,key会被回收,value可能永远不会被回收。staticclassEntryextendsWeakReference>{/**与此ThreadLocal关联的值。*/对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}所以解决方法就是使用完记得通过remove()删除。总结:ThreadLocal适用于无状态线程内部共享变量的场景。比如我们说通过ThreadLocal保存线程标识等线程相关的信息(打日志的时候适用,方便排错)。ThreadLocal有一定的内存泄漏共享,记得去掉。