IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> ThreadLocal篇 -> 正文阅读

[Java知识库]ThreadLocal篇

关于threadlocal

threadlocal的实现

ThreadLocal对象可以提供线程局部变量,每个线程Thread拥有一份自己的副本变量,多个线程互不干扰。

threadlocal的内部结构

以前的设计

每个ThreadLocal都创建一个Map,然后用线程作为Map的key,要存储的局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法在这里插入图片描述

jdk8 开始

每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。

  1. Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。
  2. ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal(它的一个弱引用),value为代码中放入的值。
  3. 每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
  4. 无论是 get()、set()在某些时候,调用了 expungeStaleEntry 方法用来清除 Entry 中 Key 为 null 的 Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。只有 remove() 方法中显式调用了 expungeStaleEntry 方法。

在这里插入图片描述

jdk8 优化后的好处

  1. 每个Map存储的Entry数量就会变少,也减少了hash冲突。因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。在实际运用当中,往往ThreadLocal的数量要少于Thread的数量。
  2. 当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。

synchronized 与 threadlocal 的区别?

synchronizedthreadlocal
原理加锁,排队访问(时间换空间)为每个线程复制一份变量副本(空间换时间)
侧重点多线程间资源的同步多线程中数据隔离

ThreadLocal.set()

  1. 首先获取当前线程,并根据当前线程获取一个Map
  2. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)
  3. 如果Map为空,则给该线程创建 Map,并设置初始值

ThreadLocal.get() 的简单理解

  1. 首先获取当前线程, 根据当前线程获取一个Map
  2. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e,否则转到4
  3. 如果e不为null,则返回e.value,否则转到4
  4. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map

总结: 先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。

ThreadLocalMap

ThreadLocalMap 的实现

类似hashmap,但没有实现map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现。

threadLocal的内存泄漏问题

  1. key以弱引用的方式指向threadlocal.
  2. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收,key也被回收了.
  3. 但是我们的value却不能回收,因为存在一条从current thread连接过来的强引用.只有当thread结束以后,强引用断开, value才会被GC回收。
  4. 所以当使用线程池的时候,线程结束是不会销毁的,会再次使用的就可能出现内存泄露 。

解决办法

  1. 避免key被自动回收,使用static修饰。 使用static final修饰threadLocal保留一个全局的threadLocal方便传递其他value(threadLocal一直被强引用)。这样就不会让gc回收作为key的threadLocal。即不会导致key为null。
  2. 手动执行remove()。使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

为什么使用弱引用而不是强引用?

使用强引用

在这里插入图片描述

  1. 假设在业务代码中使用完ThreadLocal ,threadLocal Ref被回收了。
  2. 但是因为threadLocalMap的Entry强引用了threadLocal,造成threadLocal无法被回收。
  3. 在没有手动删除这个Entry以及CurrentThread依然运行的前提下,始终有强引用链 threadRef->currentThread->threadLocalMap->entry,Entry就不会被回收(Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏。

? 也就是说,ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的。

使用弱引用

在这里插入图片描述

  1. 同样假设在业务代码中使用完ThreadLocal ,threadLocal Ref被回收了。
  2. 由于ThreadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收,此时Entry中的key=null。
  3. 但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下,也存在有强引用链 threadRef->currentThread->threadLocalMap->entry -> value ,value不会被回收, 而这块value永远不会被访问到了,导致value内存泄漏。

? 也就是说,ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。

出现内存泄漏的真实原因

  1. 没有调用 remove方法
  2. thread 线程不会被回收

为什么要使用弱引用

? 事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value回收。

? 这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。

ThreadLocalMap的 Hash 算法

下标计算方式:

int i = key.threadLocalHashCode & (len-1);

每当创建一个ThreadLocal对象,这个ThreadLocal.nextHashCode 这个值就会增长HASH_INCREMENT( 0x61c88647 )。这个值很特殊,它是斐波那契数 也叫 黄金分割数。hash增量为 这个数字,带来的好处就是 hash 分布非常均匀。

ThreadLocalMap的 Hash 冲突以及处理

  1. 虽然ThreadLocalMap中使用了黄金分割数来作为hash计算因子,大大减少了Hash冲突的概率,但是仍然会存在冲突。HashMap中解决冲突的方法是在数组上构造一个链表结构,冲突的数据挂载到链表上,如果链表长度超过一定数量则会转化成红黑树。
  2. 而 ThreadLocalMap 中并没有链表结构,所以这里不能使用 HashMap 解决冲突的方式了。而是使用线性探测法。当发生hash冲突时,就会线性向后查找,一直找到 Entry 为 null 的槽位才会停止查找,将当前元素放入此槽位中。若整个空间都找不到空余的地址,则产生溢出。可以把Entry[] table看成一个环形数组。溢出就会执行到后面的清理空闲槽位,条件满足时就会rehash();

ThreadLocalMap.set()详解

  1. 首先还是根据key计算出索引 i,然后查找i位置上的Entry,
  2. 若是Entry已经存在并且key等于传入的key,那么这时候直接给这个Entry赋新的value值,
  3. 若是Entry存在,但是key为null,则调用replaceStaleEntry来更换这个key为空的Entry,
  4. 不断循环检测,直到遇到为null的。
  5. 如果循环结束都没有return,那么就需要新建一个Entry,并且插入,同时size增加1。
  6. 最后调用cleanSomeSlots,清理key为null的Entry,再判断sz 是否达到了rehash的条件((数组长度的 2/3),达到的话就会调用rehash函数执行一次全表的扫描清理。
  7. rehash()中会先进行一轮探测式清理,清理过期key,清理完成后如果size >= threshold - threshold / 4,就会执行真正的扩容逻辑

ThreadLocalMap过期 key 的清理流程

探测式清理流程(expungeStaleEntry)

遍历散列数组,从开始位置向后探测清理过期数据,将过期数据的Entry设置为null,沿途中碰到未过期的数据则将此数据rehash后重新在table数组中定位,如果定位的位置已经有了数据,则会将未过期的数据放到最靠近此位置的Entry=null的桶中,使rehash后的Entry数据距离正确的桶的位置更近一些。

启发式清理流程(cleanSomeSlots)

在添加新元素或删除另一个陈旧元素时调用。它执行对数次扫描,循环 log2n次,如果在循环中发现了 过期key,此时会进行探测式清理,并重置循环次数为 log2n次

ThreadLocalMap的扩容

在 ThreadLocalMap.set() 方法的最后,启发式清理未删除任何条目,且散列数组中条目的数量已经达到扩容阈值,就开始执行 rehash() 逻辑。

  1. 探测式清理所有过期数据,如果依然不能缩小表空间,则扩容
  2. size >= threshold * 3/4,时执行resize
  3. 扩容后的tab的大小为原理两倍,然后遍历老的散列表,重新计算hash位置,然后放到新的tab数组中,如果出现hash冲突则往后寻找最近的entry为null的槽位,遍历完成之后,oldTab中所有的entry数据都已经放入到新的tab中,并重新计算tab下次扩容的阈值。

InheritableThreadLocal

解决子线程无法共享父线程中创建的线程副本数据问题。实现原理是子线程是通过在父线程中通过调用new Thread()方法来创建子线程,Thread#init方法在Thread的构造方法中被调用。在init方法中拷贝父线程数据到子线程中。但InheritableThreadLocal仍然有缺陷,一般我们做异步化处理都是使用的线程池,而InheritableThreadLocal是在new Thread中的init()方法给赋值的,而线程池是线程复用的逻辑,所以这里会存在问题。

当然,有问题出现就会有解决问题的方案,阿里巴巴开源了一个TransmittableThreadLocal组件就可以解决这个问题

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:54:26  更:2022-05-08 07:55:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:18:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码