概述
文章目录
- 前言
- 基础使用注意
- 注意remove()
- 源码分析
- Thread,ThreadLocal,ThreadLocalMap之间的关系
- get()和set()方法
- Entry类
- 为什么Entry是弱引用
- 强引用理解
- 软引用理解
- 弱引用理解
- 虚引用理解
- ThreadLocal避免内存泄露
- 为什么会内存泄漏
- 怎么避免
- 注意
- 最终总结
前言
深度解析一下子,基础的就略过去不讲了,想知道基础用法的请自行百度
基础使用注意
注意remove()
在每次set,get完值,在业务代码里用完了ThreadLocal变量后 一定要记得在 finally方法中调用remove()移除掉值
必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally 块进行回收
–摘自 阿里手册黄山版
源码分析
Thread,ThreadLocal,ThreadLocalMap之间的关系
Thread里面有个ThreadLocal.ThreadLocalMap threadLocals = null;变量
ThreadLocalMap属于ThreadLocal的静态内部类
get()和set()方法
其实都是操作的Thread本身自己的ThreadLocal.ThreadLocalMap对象,所以能真真正正的做到了线程之间的隔离,ThreadLocal只是提供了一个公共的入口,其实进去之后每次都会用**Thread t = Thread.currentThread();**这个方法获取当前的线程再去操作,所以看起来是调用的同一个对象,但是却能做到线程之间的隔离
Entry类
这个类是属于ThreadLocalMap的一个静态内部类,存放的是key,value(这里尤其要注意 key就是ThreadLocal本身,value就是外面你想存储的值) 这里要注意一个从属关系 就是ThreadLocalMap属于每个线程私有的 但是这个ThreadLocalMap的key值都是外面你用的那个ThreadLocal本身 由此可以猜测线程私有的这个ThreadLocalMap 是可以同时存在多个不同类型ThreadLocal对象去操作的(这块就跟普通map不一样了,普通的map一般要求是同一种类型的kv,除非你用Object基类)
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
为什么Entry是弱引用
强引用理解
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收.
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,
一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)
软引用理解
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说:
- 当系统内存充足时它不会被回收
- 当系统内存不足时它会被回收
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
弱引用理解
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存
虚引用理解
虚引用需要java.lang.ref.PhantomReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列 (ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。 仅仅是提供了一种确保对象被 finalize以后,做某些事情的机制。 PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。
其意义在于:说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
ThreadLocal避免内存泄露
为什么会内存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链。
虽然弱引用,保证了key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。
怎么避免
在finally代码块中remove掉那些null值的entry
注意
ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题。
说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量
最终总结
- ThreadLocal 并不解决线程间共享数据的问题
- ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景
- ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
- 每个线程持有一个只属于自己的专属Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
- ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
- 都会通过expungeStaleEntry,cleanSomeSlots,replaceStaleEntry这三个方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法
- 用完一定要记得remove
最后
以上就是隐形白猫为你收集整理的ThreadLocal类详细剖析的全部内容,希望文章能够帮你解决ThreadLocal类详细剖析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复