java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > ThreadLocal的用法和说明

关于ThreadLocal的用法和说明及注意事项

作者:二旬老者丶

这篇文章主要介绍了关于ThreadLocal的用法和说明及注意事项,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

ThreadLocal

ThreadLocal是用于解决Java并发安全性问题的一个类。

其主要作用是防止不同线程中的数据冲突。

原理图

如下: 

原理说明

创建一个ThreadLocal<V>类的对象,默认会在每一个线程中都开启一小片区域,该片区域可以理解为kay value格式的(实质上是在Thread中有内部类ThreadLocalMap,每声明了一个ThreadLocal,就相当于在这个ThreadLocalMap中设置了一个<key,value>,因为线程是相互独立的,所以ThreadLocalMap也是独立的),ThreadLocalMap中以ThreadLocal实例引用的变量名为key,V为value。

每一个V都是线程独有的!

使用

ThreadLocal类接口很简单,只有4个方法:

• void set(Object value)

• public Object get()

• public void remove()

• protected Object initialValue()

实例!

public final static ThreadLocal<String> threadLocal= new ThreadLocal<String>();

threadLocal代表一个能够存放String类型的ThreadLocal对象。

此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

注意!!!

ThreadLocal如果应用不妥当会导致内存泄漏。

先来说下什么是内存泄漏和内存溢出,内存泄漏是指某个变量申请了内存的资源,但是引用释放了,这样就导致占用着内存却不能访问到(俗话叫占着茅坑不拉屎!);

内存溢出是指某个变量在申请内存空间资源的时候需要的空间大于实际的空间,即为内存空间不足了(人太多坑不够了!)

如图解:

当写下 o=null时,只是表示o不再指向堆中object的对象实例,不代表这个对象实例不存在了。

下面来说明下Java中创建引用的几种方法

这里只举一个软引用的例子:

SoftReference<String> ref = new SoftReference<String>("Hello world");

这样就设置了 ref 对内存中 "Hello world"的软引用。

ThreadLocal产生内存泄漏的原因

根据我们前面对ThreadLocal的分析,我们可以知道每个Thread 拥有一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

仔细观察ThreadLocalMap,这个map是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

图中的虚线表示弱引用。

这样,当把threadlocal变量置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value永远不会被访问到了,所以存在着内存泄露。

只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是不在需要使用ThreadLocal变量后,都调用它的remove()方法,清除数据。

所以回到我们前面的实验场景,场景3中,虽然线程池里面的任务执行完毕了,但是线程池里面的5个线程会一直存在直到JVM退出,我们set了线程的localVariable变量后没有调用localVariable.remove()方法,导致线程池里面的5个线程的threadLocals变量里面的new LocalVariable()实例没有被释放。

其实考察ThreadLocal的实现,我们可以看见,无论是get()、set()在某些时候,调用了expungeStaleEntry方法用来清除Entry中Key为null的Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。只有remove()方法中显式调用了expungeStaleEntry方法。

从表面上看内存泄漏的根源在于使用了弱引用,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?

下面我们分两种情况讨论

比较两种情况,我们可以发现:

由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障。

因此,ThreadLocal内存泄漏的根源是:

由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文