0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何使用ThreadLocal来避免内存泄漏

5jek_harmonyos 来源:CSDN博客 作者:pony-zi 2021-08-20 09:29 次阅读

本次给大家介绍重要的工具ThreadLocal。讲解内容如下,同时介绍什么场景下发生内存泄漏,如何复现内存泄漏,如何正确使用它来避免内存泄漏。

ThreadLocal是什么?有哪些用途?

ThreadLocal如何使用

ThreadLocal原理

ThreadLocal使用有哪些坑及注意事项

Part1ThreadLocal是什么?有哪些用途?

首先介绍Thread类中属性threadLocals:

/* ThreadLocal values pertaining to this thread.

This map is maintained * by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

我们发现Thread并没有提供成员变量threadLocals的设置与访问的方法,那么每个线程的实例threadLocals参数我们如何操作呢?这时我们的主角:ThreadLocal就登场了。

所以有那么一句总结:

ThreadLocal是线程Thread中属性threadLocals的管理者。

也就是说我们对于ThreadLocal的get, set,remove的操作结果都是针对当前线程Thread实例的threadLocals存,取,删除操作。类似于一个开发者的任务,产品经理左右不了,产品经理只能通过技术leader来给开发者分配任务。

下面再举个栗子,进一步说明他们之间的关系:

1. 每个人都一张银行卡

每个人每张卡都有一定的余额。

每个人获取银行卡余额都必须通过该银行的管理系统。

每个人都只能获取自己卡持有的余额信息,他人的不可访问。

映射到我们要说的ThreadLocal

card类似于Thread

card余额属性,卡号属性等类似于Treadlocal内部属性集合threadLocals

cardManager类似于ThreadLocal管理类

那ThreadLocal有哪些应用场景呢?

其实我们无意间已经时时刻刻在使用ThreadLocal提供的便利,如果说多数据源的切换你比较陌生,那么spring提供的声明式事务就再熟悉不过了,我们在研发过程中无时无刻不在使用,而spring声明式事务的重要实现基础就是ThreadLocal,只不过大家没有去深入研究spring声明式事务的实现机制。后面有机会我会给大家介绍spring声明式事务的原理及实现机制。

原来ThreadLocal这么强大,但应用开发者使用较少,同时有些研发人员对于ThreadLocal内存泄漏,等潜在问题,不敢试用,恐怕这是对于ThreadLocal最大的误解,后面我们将会仔细分析,只要按照正确使用方式,就没什么问题。如果ThreadLocal存在问题,岂不是spring声明式事务是我们程序最大的潜在危险吗?

Part2ThreadLocal如何使用

为了更直观的体会ThreadLocal的使用我们假设如下场景

我们给每个线程生成一个ID。

一旦设置,线程生命周期内不可变化。

容器活动期间不可以生成重复的ID

我们创建一个ThreadLocal管理类:

2981b0cc-013e-11ec-9bcf-12bb97331649.png

测试程序如下:我们同一个线程不断get,测试id是否变化,同时测试完成后我们就将其释放掉。

29adbec4-013e-11ec-9bcf-12bb97331649.png

在主程序中我们开启多个线程测试不通线程之间是否会影响

29cdd862-013e-11ec-9bcf-12bb97331649.jpg

不出意外我们的结果为:

2a1f53c2-013e-11ec-9bcf-12bb97331649.jpg

结果:确实是不同线程间id不同,相同线程id相同。

Part3ThreadLocal原理

①ThreadLocal类结构及方法解析:

2a5b56b0-013e-11ec-9bcf-12bb97331649.png

上图可知:ThreadLocal三个方法get, set , remove以及内部类ThreadLocalMap

②ThreadLocal及Thread之间的关系:

2a8604a0-013e-11ec-9bcf-12bb97331649.png

从这张图我们可以直观的看到Thread中属性threadLocals,作为一个特殊的Map,它的key值就是我们ThreadLocal实例,而value值这是我们设置的值。

③ThreadLocal的操作过程:

我们以get方法为例:

2aa836e2-013e-11ec-9bcf-12bb97331649.png

其中getMap(t)返回的就上当前线程的threadlocals,如下图,然后根据当前ThreadLocal实例对象作为key获取ThreadLocalMap中的value,如果首次进来这调用setInitialValue()

2ac868d6-013e-11ec-9bcf-12bb97331649.png

2af774aa-013e-11ec-9bcf-12bb97331649.png

set的过程也类似:

2b14afa2-013e-11ec-9bcf-12bb97331649.png

注意:ThreadLocal中可以直接t.threadLocals是因为Thread与ThreadLocal在同一个包下,同样Thread可以直接访问ThreadLocal.ThreadLocalMap threadLocals = null;来进行声明属性。另外,欢迎关注公众号Java笔记虾,后台回复“后端面试”,送你一份面试题宝典!

Part4ThreadLocal使用有哪些坑及注意事项

我经常在网上看到骇人听闻的标题,ThreadLocal导致内存泄漏,这通常让一些刚开始对ThreadLocal理解不透彻的开发者,不敢贸然使用。越不用,越陌生。这样就让我们错失了更好的实现方案,所以敢于引入新技术,敢于踩坑,才能不断进步。

我们来看下为什么说ThreadLocal会引起内存泄漏,什么场景下会导致内存泄漏?

先回顾下什么叫内存泄漏,对应的什么叫内存溢出

Memory overflow:内存溢出,没有足够的内存提供申请者使用。

Memory leak:内存泄漏,程序申请内存后,无法释放已申请的内存空间,内存泄漏的堆积终将导致内存溢出。

显然是TreadLocal在不规范使用的情况下导致了内存没有释放。

2b9a3816-013e-11ec-9bcf-12bb97331649.png

红框里我们看到了一个特殊的类WeakReference,同样这个类,应用开发者也同样很少使用,这里简单介绍下吧

2bd74076-013e-11ec-9bcf-12bb97331649.jpg

既然WeakReference在下一次gc即将被回收,那么我们的程序为什么没有出问题呢?

①所以我们测试下弱引用的回收机制:

2beedec0-013e-11ec-9bcf-12bb97331649.png

这一种存在强引用不会被回收。

2c12f152-013e-11ec-9bcf-12bb97331649.png

这里没有强引用将会被回收。

上面演示了弱引用的回收情况,下面我们看下ThreadLocal的弱引用回收情况。

②ThreadLocal的弱引用回收情况

2c36b47a-013e-11ec-9bcf-12bb97331649.jpg

如上图所示,我们在作为key的ThreadLocal对象没有外部强引用,下一次gc必将产生key值为null的数据,若线程没有及时结束必然出现,一条强引用链Threadref–》Thread–》ThreadLocalMap–》Entry,所以这将导致内存泄漏。

下面我们模拟复现ThreadLocal导致内存泄漏:

1.为了效果更佳明显我们将我们的treadlocals的存储值value设置为1万字符串的列表:

class ThreadLocalMemory {

// Thread local variable containing each thread‘s ID

public ThreadLocal《List《Object》》 threadId = new ThreadLocal《List《Object》》() {

@Override

protected List《Object》 initialValue() {

List《Object》 list = new ArrayList《Object》();

for (int i = 0; i 《 10000; i++) {

list.add(String.valueOf(i));

}

return list;

}

};

// Returns the current thread’s unique ID, assigning it if necessary

public List《Object》 get() {

return threadId.get();

}

// remove currentid

public void remove() {

threadId.remove();

}

}

测试代码如下:

public static void main(String[] args)

throws InterruptedException {

// 为了复现key被回收的场景,我们使用临时变量

ThreadLocalMemory memeory = new ThreadLocalMemory();

// 调用

incrementSameThreadId(memeory);

System.out.println(“GC前:key:” + memeory.threadId);

System.out.println(“GC前:value-size:” + refelectThreadLocals(Thread.currentThread()));

// 设置为null,调用gc并不一定触发垃圾回收,但是可以通过java提供的一些工具进行手工触发gc回收。

memeory.threadId = null;

System.gc();

System.out.println(“GC后:key:” + memeory.threadId);

System.out.println(“GC后:value-size:” + refelectThreadLocals(Thread.currentThread()));

// 模拟线程一直运行

while (true) {

}

}

此时我们如何知道内存中存在memory leak呢?

我们可以借助jdk提供的一些命令dump当前堆内存,命令如下:

jmap -dump:live,format=b,file=heap.bin 《pid》

然后我们借助MAT可视化分析工具,来查看对内存,分析对象实例的存活状态:

首先打开我们工具提示我们的内存泄漏分析:

这里我们可以确定的是ThreadLocalMap实例的Entry.value是没有被回收的。

最后我们要确定Entry.key是否还在?打开Dominator Tree,搜索我们的ThreadLocalMemory,发现并没有存活的实例。

以上我们复现了ThreadLocal不正当使用,引起的内存泄漏。

文中源码:

https://github.com/z-one/basictest/tree/master/src/main/java/com/spring/test/threadlocal

所以我们总结了使用ThreadLocal时会发生内存泄漏的前提条件:

ThreadLocal引用被设置为null,且后面没有set,get,remove操作。

线程一直运行,不停止。(线程池)

触发了垃圾回收。(Minor GC或Full GC)

我们看到ThreadLocal出现内存泄漏条件还是很苛刻的,所以我们只要破坏其中一个条件就可以避免内存泄漏,单但为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则:

①ThreadLocal申明为private static final。

Private与final 尽可能不让他人修改变更引用,

Static 表示为类属性,只有在程序结束才会被回收。

②ThreadLocal使用后务必调用remove方法。

最简单有效的方法是使用后将其移除。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    2996

    浏览量

    73868
  • 内存泄露
    +关注

    关注

    0

    文章

    6

    浏览量

    1978

原文标题:ThreadLocal理解, 运用以及内存泄漏的处理方案

文章出处:【微信号:harmonyos_developer,微信公众号:harmonyos_developer】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    ThreadLocal实例应用

    ThreadLocal相信大家都用过,但你知道他的原理吗,今天了不起带大家学习ThreadLocalThreadLocal是什么 在多线程编程中,经常会遇到需要在不同线程中共享数据的情况
    的头像 发表于 09-30 10:19 636次阅读
    <b class='flag-5'>ThreadLocal</b>实例应用

    ThreadLocal的定义、用法及优点

    ThreadLocal 简介 ThreadLocal是Java中一个非常重要的线程技术。它可以让每个线程都拥有自己的变量副本,避免了线程间的竞争和数据泄露问题。在本文中,我们将详细介绍
    的头像 发表于 09-30 10:14 993次阅读
    <b class='flag-5'>ThreadLocal</b>的定义、用法及优点

    内存泄漏定位该如何去实现呢

    。对于内存泄漏的情况,如果一开始不做预防,定位内存泄漏就会相当繁琐,定位也会很长,非常的耗时、耗力。这里可通过malloc、free的第二次封装
    发表于 12-17 07:24

    ThreadLocal发生内存泄漏的原因

    ,就可能会导致内存泄漏。下面,我们将围绕三个方面分析 ThreadLocal 内存泄漏的问题
    的头像 发表于 05-05 16:23 3653次阅读

    内存泄漏的特点和类型

    在计算机科学中,内存泄漏(memory leak)指由于疏忽或错误使程序未能释放而造成不能再使用的内存的情况。内存泄漏并非指
    的头像 发表于 06-20 10:58 2787次阅读

    内存泄漏问题原理及检视方法

    可能不少开发者都遇到过内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象。一方面,内存泄漏问题属于比较
    的头像 发表于 10-10 10:42 2505次阅读

    如何避免内存泄漏的方法和原则

    本文向读者介绍了如何避免内存泄漏的方法和原则,在细节和大体方向上均给出一些可行性方案。读者可以尝试文中提出的方法,改进自己的代码,大大减少内存泄漏
    的头像 发表于 10-21 14:30 5862次阅读
    如何<b class='flag-5'>避免</b><b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>的方法和原则

    什么是内存泄漏内存泄漏有哪些现象

    内存泄漏几乎是很难避免的,不管是老手还是新手,都存在这个问题,甚至 Windows 与 Linux 这类系统软件也或多或少存在着内存泄漏
    的头像 发表于 09-05 17:24 9599次阅读

    ThreadLocal基本内容与用法

    下面我们就来看看道哥都用的ThreadLocal。 1 ThreadLocal你来自哪里 Since : 1.2 Author : Josh Bloch and Doug Lea 又是并发大佬们
    的头像 发表于 10-13 11:39 431次阅读

    什么是内存泄漏?如何避免JavaScript内存泄漏

    JavaScript 代码中常见的内存泄漏的常见来源: 研究内存泄漏问题就相当于寻找符合垃圾回收机制的编程方式,有效避免对象引用的问题。
    发表于 10-27 11:30 358次阅读
    什么是<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>?如何<b class='flag-5'>避免</b>JavaScript<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>

    内存泄漏如何避免

    的数,那就是内存溢出。 2. 内存泄漏 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的
    的头像 发表于 11-10 11:04 703次阅读
    <b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>如何<b class='flag-5'>避免</b>

    内存泄漏会产生哪些后果

    内存泄漏原因 内存泄漏在C/C++这种不带GC(Garbage Collection)的语言里,是一个经常发生的问题。因为没有GC,所以分配的内存
    的头像 发表于 11-10 15:06 757次阅读
    <b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>会产生哪些后果

    线程内存泄漏问题的定位

    记录一个关于线程内存泄漏问题的定位过程,以及过程中的收获。 1. 初步定位 是否存在内存泄漏:想到内存
    的头像 发表于 11-13 11:38 580次阅读
    线程<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题的定位

    内存溢出与内存泄漏:定义、区别与解决方案

    内存溢出与内存泄漏:定义、区别与解决方案  内存溢出和内存泄漏是计算机科学中常见的问题,在开发和
    的头像 发表于 12-19 14:10 2451次阅读

    C语言内存泄漏问题原理

    内存泄漏问题只有在使用堆内存的时候才会出现,栈内存不存在内存泄漏问题,因为栈
    发表于 03-19 11:38 477次阅读
    C语言<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题原理