ThreadLocal是如何存储数据的?理解源码,规避ThreadLocal坑


通常我们为了规避单例模式下多线程之间共享全局变量的问题,会使用ThreadLocal,不过很多人不知道ThreadLocal里数据的存储方式和我们平时理解的不太一样。

 

源码解析(直奔主题):

每个线程都有一个ThreadLocalMap对象:

 

 

这个Map里存了所有线程变量中保存的数据,保存的方式:

 

以ThreadLocal对象作为key,ThreadLocal里“存放”的数据作为value,放在该Map中。

 

 

 

在我们创建了一个线程变量 maxLife=new ThreadLocal<Integer>(),之后执行其set(xxx)方法,其实是以maxLife这个对象为键,以xxx为值,将这组键值对放入当前线程的ThreadLocalMap对象中。

 

这种方式的存储有一个坑必须注意:

ThreadLocal对象一旦创建不可轻易替换,替换会导致所有共享该ThreadLocal对象的线程均无法找到之前为其“保存”的值。

 

打个比方

张三(Thread)和李四(Thread)各有一个一模一样盒子(ThreadLocalMap)。

这是个神奇的盒子,可以用任何一个钥匙(ThreadLocal对象)打开,每个钥匙打开盒子都会打开他们自己的存储空间,和别的钥匙互不干扰。

凑巧张三李四共用一个钥匙包(ThreadLocal对象的引用),这个钥匙包里只存放了一个钥匙(ThreadLocal对象)。

有一天张三用这个钥匙包里的钥匙存了一个兔子在盒子里,李四也做了同样的事

接着张三把钥匙包里的钥匙换掉了,那么李四再用这个钥匙包里的钥匙去开盒子,会发现,兔子不见了!

 

通过伪代码再现坑

为了说明这个坑,我们再上一些伪代码:

首先,我们在类中定义一个全局变量,后边的所有线程都会共享这个变量。

ThreadLocal<Integer> maxLife = null;

 

然后创建一个线程A,并在这个线程中为maxLife赋一个新对象,并且为其设置值

maxLife = new ThreadLocal<Integer>()
maxLife.set(0);

 

之后暂停A线程的执行,同时我们创建一个B线程,然后在B线程里,将maxLife指向一个新的对象

 

maxLife = new ThreadLocal<Integer>();

 

执行完这一段,暂停B的执行,然后再回到A线程,执行

Integer i = maxLife.get();

 

这个时候你如果打印i,那么你会看到一个null而不是0。

 

这是为什么呢?

因为maxLife的get方法,其实是以maxLife这个对象本身为键,然后去当前线程的ThreadLocalMap中获取其所对应的值,A线程中我们new了一个对象然后当成key,B线程中我们又new了一个,我们知道A和B先后new 的两个对象不是同一个对象,那么以不同的对象作为key去一个map里面取值会怎样呢?

 

也就是说 maxLife 这个变量,在A和B中是共享的,但是maxLife作为key在A和B各自的ThreadLocalMap中对应的值是不一样的。

 

总结

所以我们定义ThreadLocal变量,应该在一开始就为其new一个值,使用过程中避免修改其引用指向对象,而不是让它等于null。

并且使用完毕后调用其remove()方法,避免产生内存溢出。

 

 

完毕。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM