软件编程
位置:首页>> 软件编程>> java编程>> Java中ThreadLocal避免内存泄漏的方法详解

Java中ThreadLocal避免内存泄漏的方法详解

作者:越走越远的风  发布时间:2023-04-02 12:51:42 

标签:Java,ThreadLocal,避免,内存,泄漏

ThreadLocal简介

ThreadLocal 是 Java 中的一个线程本地存储机制,它允许每个线程拥有一个独立的本地存储空间,用于存储该线程的变量。ThreadLocal 提供了一种简单的方式来解决多线程环境下共享变量的问题,避免了在多线程环境下出现的线程安全问题。

ThreadLocal简单用法

public class ThreadLocalDemo {  
   private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);  

//当前值+1
   public static void increment() {  
       int value = threadLocal.get();  
       threadLocal.set(value + 1);  
   }  

public static void main(String[] args) throws InterruptedException {  
       for (int i = 0; i < 10; i++) {  
           new Thread(() -> {  
               String threadName = Thread.currentThread().getName();  
               increment();  
               System.out.println(threadName + " 当前threadLocal的值为:" + threadLocal.get());  
           }).start();  
       }  
   }  

}

输出结果为

Thread-0 当前threadLocal的值为:1
Thread-5 当前threadLocal的值为:1
Thread-3 当前threadLocal的值为:1
Thread-4 当前threadLocal的值为:1
Thread-6 当前threadLocal的值为:1
Thread-2 当前threadLocal的值为:1
Thread-9 当前threadLocal的值为:1
Thread-1 当前threadLocal的值为:1
Thread-7 当前threadLocal的值为:1
Thread-8 当前threadLocal的值为:1

我们发现每个线程虽然都共享同一个threadLocal实例,但它们并没有发生相互干扰的情况,而是各自产生独立的值,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

使用场景

spring事务模板类

//使用示例
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

//查看源码
public static TransactionStatus currentTransactionStatus() throws NoTransactionException {  
   TransactionInfo info = currentTransactionInfo();  
   if (info == null || info.transactionStatus == null) {  
       throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");  
   }  
   return info.transactionStatus;  
}

//currentTransactionInfo方法
@Nullable  
protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {  
   return transactionInfoHolder.get();  
}

//这里使用了ThreadLocal
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =  
new NamedThreadLocal<>("Current aspect-driven transaction");

HttpServletRequest

项目中要获取当前HttpServletRequest可以使用

HttpServletRequest request =  
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

我们来看看RequestContextHolder.getRequestAttributes()方法

@Nullable  
public static RequestAttributes getRequestAttributes() {  
   RequestAttributes attributes = requestAttributesHolder.get();  
   if (attributes == null) {  
       attributes = inheritableRequestAttributesHolder.get();  
   }  
   return attributes;  
}

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =  
new NamedThreadLocal<>("Request attributes");  

private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =  
new NamedInheritableThreadLocal<>("Request context");

aop调用链传递

LCN/seata都是使用ThreadLocal传递调用链的,这里就不展开讲了。

内存泄漏与内存溢出

内存泄漏

内存泄漏指的是程序中存在某些对象或资源没有被妥善地释放,导致这些对象或资源一直占用着内存,而无法被回收。随着时间的推移,这些未释放的对象或资源会越来越多,最终耗尽系统的内存资源,导致系统崩溃。

常见的内存泄漏包括:

  • 对象被创建后,没有及时被销毁,成为垃圾对象。

  • 没有正确关闭IO资源。

  • 缓存没有被清空。

  • 静态集合类对象未删除引用。

  • 单例模式下对象未及时释放等。

内存溢出

内存溢出指的是程序在申请内存时,无法获得足够的内存空间,导致程序无法正常运行。通常情况下,当程序需要使用的内存超过了系统能够提供的内存时,就会发生内存溢出。

常见的内存溢出包括:

  • 堆内存溢出:由于创建了过多的对象或者某些对象太大,导致堆内存不足。

  • 栈内存溢出:由于方法调用过多或者某些方法的递归调用层数过多,导致栈内存不足。

  • 永久代内存溢出:由于创建了过多的类或者字符串,导致永久代内存不足。

区别

内存泄漏和内存溢出的区别在于它们发生的原因和表现形式。内存泄漏是指对象或者资源无法被妥善释放,导致系统资源浪费,而内存溢出则是指系统不能分配所需内存,导致程序崩溃或者异常。通常情况下,内存泄漏会逐渐消耗系统资源,而内存溢出则是突然发生的。

解决内存泄漏的方法是找到未被正确释放的对象或资源,并手动进行释放。而解决内存溢出的方法则需要优化程序代码,减少内存使用量,或增加系统内存大小等方式来解决。

java强软弱虚

Java中的引用类型有四种:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。它们之间的主要区别在于对象被垃圾回收时的行为不同。

强引用

强引用是默认类型的引用,当我们通过&ldquo;new&rdquo;关键字创建一个对象时,该对象会被分配到堆内存中,并且默认情况下,该对象的引用是强引用。只要强引用存在,垃圾回收器就不会将其回收。

例如:

Object obj = new Object();

在上面的代码中,obj是一个强引用,因此只有当obj变量被显示地设置为null时,才能使对象成为垃圾,等待垃圾回收器收集。

软引用

软引用可以让对象存活更长时间,直到内存不足时才回收它。如果垃圾回收器需要更多的内存,则会回收只被软引用引用的对象。当一个对象只被软引用引用时,它会被保留在内存中,直到系统内存不够用或者垃圾回收器需要更多空间为止。通过软引用可以实现一些缓存功能。

例如:

SoftReference<Object> softRef = new SoftReference<>(new Object());

在上面的代码中,softRef是一个软引用,当垃圾回收器需要内存时,它可以将该对象回收,并释放所占用的内存。

弱引用

弱引用比软引用生命期更短,当一个对象只被弱引用引用时,当垃圾回收器运行时,不管当前内存是否充足,都会将其回收。弱引用通常用于实现缓存机制或者观察者模式。

例如:

WeakReference<Object> weakRef = new WeakReference<>(new Object());

在上面的代码中,weakRef是一个弱引用,这意味着垃圾回收器可以随时将该对象回收,而无需考虑系统内存是否充足。

虚引用

虚引用也称为幽灵引用,与其他三种引用方式不同,它并不会决定对象是否能存活。如果一个对象只有虚引用,那么就像没有任何引用一样,它的内存会被回收,但是在回收之前会调用finalize()方法。虚引用主要用于管理DirectBuffer的生命周期。

例如:

PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), null);

在上面的代码中,phantomRef是一个虚引用,当垃圾回收器发现该对象的内存已被回收时,它会将其插入队列中,并在下一次调用垃圾回收器时通知引用对象被回收了。

ThreadLocal原理

ThreadLocal

ThreadLocal是一个泛型类,它提供了get()、set()和remove()方法来获取、设置和删除当前线程的变量副本。它的原理是在每个Thread对象中都有一个ThreadLocalMap类型的私有变量threadLocals,该变量存储着当前线程所对应的所有ThreadLocal变量的值。

public T get() {
   //获取当前线程
   Thread t = Thread.currentThread();  
   //获取当前线程的ThreadLocalMap变量
   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();  
}

public void set(T value) {  
   Thread t = Thread.currentThread();  
   ThreadLocalMap map = getMap(t);  
   if (map != null)  
       map.set(this, value);  
   else  
       createMap(t, value);  
}

public void remove() {  
   ThreadLocalMap m = getMap(Thread.currentThread());  
   if (m != null)  
       m.remove(this);  
}

ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,它实际上就是一个HashMap,用于存储当前线程所对应的所有ThreadLocal变量的值。每个ThreadLocal对象都会被保存在ThreadLocalMap中,并且使用ThreadLocal作为key来访问它的变量值。这样做的好处是每个线程都可以独立维护自己的数据,而不会与其他线程产生冲突。

ThreadLocalMap getMap(Thread t) {  
   return t.threadLocals;  
}

ThreadLocal.ThreadLocalMap threadLocals = null;

这里需要注意的是ThreadLocalMap每个元素都是Entry,而它是用弱引用对象作为key存储在ThreadLocalMap中


static class Entry extends WeakReference<ThreadLocal<?>> {  
   /** The value associated with this ThreadLocal. */  
   Object value;  

Entry(ThreadLocal<?> k, Object v) {  
       super(k);  
       value = v;  
   }  
}

实现原理

当我们通过ThreadLocal类创建一个新的变量时,实际上是在当前线程的threadLocals变量中创建了一个新的Entry对象,该对象的key是ThreadLocal对象本身,value则是我们设置的变量值。这个Entry对象会存储在ThreadLocalMap中。

当我们需要在当前线程中访问这个变量时,ThreadLocal会根据当前线程获取对应的ThreadLocalMap对象,并根据ThreadLocal对象作为key来查找该变量的值。由于每个线程都有自己独立的ThreadLocalMap对象,因此不同线程之间的变量互不干扰。

ThreadLocal内存泄漏原因

每个ThreadLocal对象都会被存储在当前线程的ThreadLocalMap中,并且使用ThreadLocal对象作为key来访问它的变量值。由于使用的是弱引用对象作为key,当一个ThreadLocal对象没有被任何线程引用时,该对象就会被回收。

但是,即使ThreadLocal对象已经被回收,对应的变量副本仍然存在于该线程的ThreadLocalMap中。这是因为ThreadLocalMap内部使用了强引用对象(Entry对象)来引用变量副本,只有在当前线程被回收时,ThreadLocalMap中对应的Entry才会被回收。

也就是说,ThreadLocal对象虽然使用的是弱引用,但是与之关联的变量副本却是通过强引用对象间接引用的,因此在ThreadLocal对象被回收后,其变量副本可能不会立刻被回收。如果我们没有手动调用remove()方法将变量副本从ThreadLocalMap中清除,那么它就会一直存在于内存中,从而导致内存泄漏问题。

ThreadLocal内存泄漏常见场景

ThreadLocal内存泄漏的原因主要是由于线程复用导致的。

线程池

当我们使用线程池时,如果在线程中使用了ThreadLocal变量,那么该变量并不会被自动清除。线程池中的线程是可以被重复利用的,如果我们在一个线程中使用了ThreadLocal变量,并且没有在该线程结束前手动清除它,那么这个变量将会一直存在于ThreadLocalMap中,即使该线程已经被回收,这就会导致内存泄漏。

长时间持有

如果我们在一个线程中创建了ThreadLocal变量,并且一直持有它却不使用,这也会导致内存泄漏问题。在这种情况下,由于该变量一直存在于ThreadLocalMap中,即使该线程已经被回收,该变量也无法被释放,最终会导致内存泄漏。

ThreadLocal避免内存泄漏方法

为了避免ThreadLocal内存泄漏问题,我们可以采取以下措施:

  • 在使用完ThreadLocal变量后,应该尽快调用remove()方法将其从ThreadLocalMap中清除,以便让垃圾回收器回收它们。

  • 将ThreadLocal变量定义成private static类型的,并且在使用完之后手动清除,以避免线程重用时引起的内存泄漏问题。

  • 不要在线程池中使用ThreadLocal变量,如果必须使用,应该在使用完后手动清理。

来源:https://juejin.cn/post/7234365864842297401

0
投稿

猜你喜欢

  • Java基础将Bean属性值放入Map中的实例利用发射将Java对象的属性值以属性名称为键,存储到Map中的简单实现。包括自身属性及从父类继
  • main方法调用spring的service将业务层类配置到Spring中:<bean id="customerServic
  • C#重绘checkbox生成滑动开关,供大家参考,具体内容如下通过调用checkbox控件的paint事件,在重绘事件里判断checked属
  • 本文实例为大家分享了java模拟斗地主发牌的具体代码,供大家参考,具体内容如下1.案例介绍规则:组装54张扑克牌54张牌顺序打乱三个玩家参与
  • 双保险线程,每次启动2个相同的线程,互相检测,避免线程死锁造成影响。两个线程都运行,但只有一个线程执行业务,但都会检测对方的时间戳 如果时间
  • 前言最近做一个微信公众号服务,有一些简单的图片处理功能。主要就是用户在页面操作,前端做一些立刻显示的效果,然后提交保存时后端真正修改原图。从
  • 这两天因为要做一个随机的地图生成系统,所以一直在研究随机迷宫生成算法,好吧,算是有一点小小的成果。随机迷宫生成我自己的理解简而言之分为以下几
  • 公钥加密算法,也就是 非对称加密算法,这种算法加密和解密的密码不一样,一个是公钥,另一个是私钥:公钥和私钥成对出现公开的密钥叫公钥,只有自己
  • 一、作用及种类UML类图建模语言或标准建模语言类的属性、操作中的可见性使用+、#、-分别表示public、protected、private
  • 在Java的学习中,涉及到两个系统环境变量path和classpath一. path环境变量path环境变量是系统环境变量的一种,它用于保存
  • 前言Windows 11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化,只是这是默认的行为,无法进一步自定义。注意两点:Pain
  • 在wpf中实现treeview的功能,可能看到很多分享的都是简单的绑定,仅此记录自己完成的功能。前台<TreeView x:Name=
  • MyBatis查询返回null可能原因SQL语句查询条件有问题数据库中没数据返回字段与Entity的属性不对应解决方案针对 SQL
  • 简述:JRebel是一款JVM插件,它使得Java代码修改后不用重启系统,立即生效。IDEA上原生是不支持热部署的,一般更新了 Java 文
  • 因为系统的菜单列表是不轻易改变的,所以不需要在每次请求的时候都去查询数据库,所以,在第一次根据用户id请求到菜单列表的时候,可以把菜单列表的
  • 在项目开发中,经常碰到map转实体对象或者对象转map的场景,工作中,很多时候我们可能比较喜欢使用第三方jar包的API对他们进行转化,而且
  • 一.话题引入在做项目过程中,我们一般都是最先编写登录注册功能,登录功能最重要的是登录成功后,系统还会保存该登录用户信息,这种保存用户信息的逻
  • 1.IO流介绍IO流可以用到的地方很多,就比如设计模式、下载、传输等等。学好IO流,为之后的进一步学习打下基础,那么,先来说说什么是流?流是
  • 需求:字符串(字符串只有一位小数)转float进行运算, 将结果转成字符串(保留一位小数)直接上代码:float f1 = 0.1f;Str
  • 一、基本概念C#只有两种数据类型:值类型和引用类型值类型在线程栈分配空间,引用类型在托管堆分配空间值类型转为引用类型称成为装箱,引用类型转为
手机版 软件编程 asp之家 www.aspxhome.com