ThreadLocal的作用
是一个线程内部的存储类
- 本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程就都会操作该副本,从而完全规避了多线程的并发问题。
基本使用
- 代码
public class ThreadLocalT {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();//维护的变量类型是String
threadLocal.set("hello world");//set值
System.out.println(threadLocal.get());//获取值
threadLocal.set("Kitty Guy");//set值,会覆盖之前值
System.out.println(threadLocal.get());//获取的是最新set的值
}
}
复制代码
大致了解ThreadLocal原理
构造方法
先看get方法
- 里面的getMap方法
- 里面ThreadLocalMap
实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。
再看set方法
关于引用
- Java中存在四种类型的引用:
- 强引用(strong):不会被垃圾回收。
- 软引用(soft):内存不够的情况下,会被垃圾回收。
- 弱引用(weak):下一次垃圾回收时,会被回收。前提是没有被强引用再去指向它。
- 虚引用(phantom):不指向对象,当对象被回收时,会收到通知,进行后续操作处理。
它们有垃圾回收时机上的区别,除了强引用,其他引用都要继承
Reference<T>
内存泄漏
ThreadLocal在的JVM内存情况
关键点就Entry数组下标(K)是ThreadLocal的引用
从两个角度分析内存泄漏
先假设K的引用是强引用
- 当程序运行到某一时刻,需要销毁ThreadLocal对象,它先会销毁栈中的ThreadLoacl的引用。
- 由于堆中的Entry数组的下标K是ThreadLocal的强引用,这就导致虽然栈中的引用销毁了,但是堆中的没有被销毁,且无法销毁。这就进一步导致了。
- Entry数组里的内容无法清除,如果重复多次使用同一个Entry数组,就导致上述情况累积发生,然后数组爆满,造成内存泄漏。
- 即 K和V 存进去就无法清除,累积后数组爆满,内存泄漏。
既然强引用不好用,就用其它引用
实际情况K的引用是弱引用
单独一个弱引用,也无法解决内存泄漏
- 当程序运行到某一时刻,需要销毁ThreadLocal对象,它先会销毁栈中的ThreadLoacl的引用。
- 栈中的ThreadLocal强引用被销毁,由于堆中的Entry数组的下标K是ThreadLocal的弱引用,在下一次垃圾会回收时,因堆中的ThreadLocal没有被强引用,遂会被销毁。至此
- K无法被消除的情况解决了。但是
- V却没有删除,就导致了Entry中空有V却没有对应的数组下标与其对应,如此下去,就会导致数组会有很多的V没有对应的K,V的数量增加下去,导致Entry数组爆满,内存泄漏。
弱引用没有彻底解决内存泄漏咋整?
在代码层面进行一番操作即可
- jdk源代码
在调用ThreadLocal的get、set等方法时,都会对Entry数组没有K的Value进行清除,因为弱引用会清除Entry数组的K,但不会清除的Value,只要清除了Value就解决了内存泄漏。
在ThreadLocal的实际应用中应该使用下面的形状,来防止内存泄漏
public class ThreadLocalT {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();//维护的变量类型是String
try{
......
......
}finally {
threadLocal.remove();//很关键,防止内存泄漏
}
}
复制代码
- 因为弱引用需要移除强引用的基础上,才能被垃圾回收
- 调用remove方法就是移除栈中的强引用。才能确保不会发生内存泄漏。
小结
- ThreadLocal不是真正存储变量的,ThreadLocalMap中Entry数组才是。
- ThreadLocalMap是Thread类中的成员变量。
- 调用ThreadLocal的get、set和remove方法会清除Entry数组中没有K的Value。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END