ThreadLocal

在这里插入图片描述

ThreadLocal介绍

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

主要解决了让每个线程绑定自己的值,通过使用get0)和set()方法,获取默认值或将其值改为当前线程所存的副本的值,从而避免了线程安全间题

ThreadLocal api 基本使用

需求:5个销售买房子,高层只关心销售总量的准确统计数

案例实现:
在这里插入图片描述

现在需求变了,销售自己卖的越多提成越高
案例实现:
在这里插入图片描述
在这里插入图片描述
以上案例我们是new Thread()的方式去使用,问题不到,但是如果是线程池的场景下就会存在问题
在这里插入图片描述

那么我们这就要带上finally加remove
在这里插入图片描述
如果用线程池会出现什么问题呢?我们用案例来说话
在这里插入图片描述
在这里插入图片描述
线程复用,会导致上一个线程里面存在之前遗留的值,此时加上remove,线程就干净了
在这里插入图片描述

ThreadLocal源码解析

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

查看ThreadLocal.set()方法,我们可以看到里面有Thread和ThreadLocalMap,那么他们三者之间有什么关系呢?

Thread、ThreadLocal、ThreadLocalMap

让我们来看看Thread和ThreadLocalMap的源码
在这里插入图片描述
在这里插入图片描述
看过map源码的可能很快就能理解,threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象,当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,
值为value的Entry往这个threadLocalMap中存放
在这里插入图片描述
JVM内部维护了一个线程版的Map<ThreadLocal.Value>(通过ThreadLocal对象的set方法,结果把ThreadLoca对象自己当做key放进了ThreadLoalMap中),每个线程要用到这个T的时候,用当前的线程去Map里面获取,通过这样让每个线程都拥有了自己独立的变量,人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量。

ThreadLocal 内存泄露问题

什么是内存泄漏:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

再回顾ThreadLocal为什么要最后加remove
在这里插入图片描述
ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(以ThreadLocal为Key),不过是经过了两层包装的ThreadLocal对象

(1)第一层包装是使用 WeakReference<ThreadLocal<?>> 将ThreadLocal对象变成一个弱引用的对象

(2)第二层包装是定义了一个专门的类 Entry 来扩展WeakReference<ThreadLocal<?>>

WeakReference是弱引用,后面先来了解下什么是弱引用,有弱引用是不是还有强引用、软引用等等

在这里插入图片描述

强引用 Reference

当内存不足,JVM死始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。

强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。

在Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。

当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 nul,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。

示例:
在这里插入图片描述
强引用对象被置空,gc回收成功
在这里插入图片描述

软引用 SoftReference

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。

对于只有软引用的对象来说,

  • 当系统内存充足时它不会被回收,
  • 当系统内存不足时它会被回收。

软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

案例:
在这里插入图片描述
添加IDEA的内存使用限制最多使用10兆:-Xms10m -Xmx10m
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

弱引用 WeakReference

弱引用需要用java.ang.ref.WeakReference类来实现,它比软引用的生存期更短,

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
在这里插入图片描述

假如有一个应用需要读取大量的本地图片:

  • 如果每次读取图片都从硬盘读取则会严重影响性能,
  • 如果一次性全部加载到内存中又可能造成内存溢出。

此时使用软引用可以解决这个问题。

设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SofReference<Bitmap>>();

虚引用 PhantomReference

1、虚引用必须和引用队列(ReferenceQueue)联合使用
虚引用需要java.ang.ref.PhantomReference类米实现,顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。

2 PhantomReference的get方法总是返回null
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize以后,做某些事情的通知机制。
PhantomReference的get方法总是返回nul,因此无法访问对应的引用对象。

3 处理监控通知使用
换句话说,设置虚引用关联对象的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处,用来实现比finalize机制更灵活的回收操作

案例:
在这里插入图片描述

为什么ThreadLocal要用到弱引用

当function01方法执行完毕后,栈顿销段强引用tl也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象

若这个key引用是强引用,就会导致key指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏

若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为null的雷,第2个坑后面讲)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后,顺利被回收目Entry的key引用指向为null。
在这里插入图片描述
在这里插入图片描述

HashMap<String, Object> map = new HashMap<>(1);
map.put(null,"key是null,value照样存");

就好比以上代码,不会出错,即使ThreadLocalMap的key为null,value还是存在,这样还是会有强引用链,导致gc无法回收,所以在使用线程池线程被复用的情况下,需要我们手动调用remove方法,让ThreadLocal清理value,解决安全漏洞
在这里插入图片描述
在这里插入图片描述

总结

  • ThreadLocal并不解决线程间共享数据的问题
  • ThreadLocal适用于变量在线程间隔离且在方法间共享的场景
  • ThreadLocal通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程持有一个只属于自己的专属Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
  • 都会通过expungeStaleEntry,cleanSomeSlots,replaceStaleEntry这三个方法回收键为 null 的 Entry对象的值(即为具体实例)以及Entry对象本身从而防止内存泄漏,属于安全加固的方法

相关文献

ThreadLocal:
https://www.runoob.com/manual/jdk11api/java.base/java/lang/ThreadLocal.html

就先说到这 \color{#008B8B}{ 就先说到这} 就先说到这
在下 A p o l l o \color{#008B8B}{在下Apollo} 在下Apollo
一个爱分享 J a v a 、生活的小人物, \color{#008B8B}{一个爱分享Java、生活的小人物,} 一个爱分享Java、生活的小人物,
咱们来日方长,有缘江湖再见,告辞! \color{#008B8B}{咱们来日方长,有缘江湖再见,告辞!} 咱们来日方长,有缘江湖再见,告辞!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值