栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal原理以及内存泄漏问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal原理以及内存泄漏问题

文章目录
        • 什么是ThreadLocal?有哪些应用场景?
        • ThreadLocal 原理
        • ThreadLocal 内存泄漏问题
        • 为什么要将key设计成ThreadLocal的弱引用?


什么是ThreadLocal?有哪些应用场景?

ThreadLocal类可以让每个线程绑定自己的值,也就是拥有自己的专属本地变量。

ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,并且不会和其他线程的变量冲突,实现了线程间的数据隔离,避免了线程安全问题。

ThreadLocal的应用场景主要有以下几个方面:

  • 保存线程上下文信息,避免参数的显示传递,在需要的地方可以直接获取
  • 线程间数据隔离
  • 进行事务操作时存储线程事务信息,因为事务和线程绑定在一起(Spring在事务开始时会给当前线程绑定一个Jdbc Connection对象,放在ThreadLocal中存储,这样在整个事务执行过程中都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性)
  • 数据库连接(经典的使用场景是为每个线程分配一个JDBC Connection连接对象,这样可以保证每个线程的都在各自的Connection上进行数据库的操作,不会出现A线程关了B线程正在使用的Connection)
  • session会话等线程级别的操作(Session 的特性很适合 ThreadLocal ,因为 Session 之前当前会话周期内有效,会话结束便销毁)

ThreadLocal 原理

从Thread类的源代码可以看出Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,实际上当前线程调用 ThreadLocal 类的 set或get方法时,我们调用的是当前线程的ThreadLocalMap类对应的 get()、set()方法。

public class Thread implements Runnable {
	
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

ThreadLocal 是线程本地存储,每个线程中都具备一个ThreadLocalMap,ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。ThreadLocalMap是ThreadLocal的内部类,可以理解为一个Map容器,其维护了一个 Entry 数组,由一个个key-value对象Entry构成。

由于每一条线程都含有线程私有的ThreadLocalMap容器,这些容器间相互独立不影响,因此不会存在线程安全的问题,从而无需使用同步机制来保证多条线程访问容器的互斥性。

  • 使用set方法时:ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象,再以当前的ThreadLocal对象为key,将值存入ThreadLocalMap对象中。
  • get方法执行过程类似,首先ThreadLocal获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前的ThreadLocal对象为key,获取对应的value。
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value); //key为当前ThreadLocal对象,value为set的值
    else
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); //key为当前ThreadLocal对象,获取绑定的值
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocal 内存泄漏问题

**内存泄露 **:指的是为程序在申请内存后,无法释放已申请的内存空间。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存迟早会被占光。简单来说,不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

与OOM的区别:内存泄漏是内存占用无法释放,而OOM是内存不够,内存泄漏会导致OOM。

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry,而value还存在着强引用。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法。

看到Entry继承自 WeakReferencr>,就是一个 key-value 形式的对象。它的 key 就是 ThreadLocal 对象,并且是一个弱引用,如果没有指向 key 的强引用后,该 key 就会被垃圾回收器回收;Entry 的 value 就是存储 Object 对象,是强引用。

static class ThreadLocalMap {
    
    static class Entry extends WeakReference> {
        
        Object value;

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

强引用:

指在代码之中普遍存在的引用赋值,即使用 new 对象创建强引用。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会对被引用的对象进行回收。

Object obj = new Object();

如果想要取消强引用和某个对象之间的关联,可以显示将引用赋值为null,或者超过了引用的作用域时,如方法结束,就可以当作垃圾回收,这样JVM就可以在合适的时间对其回收。

弱引用:

也是用来描述那些非必须对象,但被弱引用关联的对象不管内存是否足够都一定会被回收,也就是说它只能存活到下一次垃圾回收发生为止,比起软引用,只具有弱引用的对象拥有更短暂的生命周期。可以使用 WeakReference 类来创建弱引用。


为什么要将key设计成ThreadLocal的弱引用?

如果key是强引用,同样会发生内存泄漏的。引用ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

如果是弱引用的话,引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收,此时的key为null,但在下一次调用ThreadLocalMap的set()、get()、remove()方法时,会清除 key 为 null 的 value 值,避免内存泄漏。

因此,ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

所以两种方案比较下来,还是ThreadLoacl的key为弱引用好一些。

ThreadLocal的正确使用方法:

  • 当ThreadLocal作为局部变量时,每次使用完(方法结束)都调用其 remove() 方法清除数据(生命周期不需要和项目的生存周期一样长的)。
  • 将ThreadLocal变量定义成为private static,这样就一直存在ThreadLocal的强引用,ThreadLocal就不会轻易被回收,可以保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉无用的value。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/606160.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号