ThreadLocal 原理

本文深入探讨了Java中的ThreadLocal原理,包括其线程本地变量特性,如何使用set()、get()、initialValue()和remove()方法。还讨论了ThreadLocalMap的Entry实体类及其在内存管理中的角色,以及如何防止内存泄露。此外,文章对比了ThreadLocal与synchronized在处理多线程并发访问时的不同策略:ThreadLocal提供线程间的数据隔离,而synchronized则实现数据共享。

ThreadLocal 线程本地变量

ThreadLocal 线程本地变量,即每个线程可以有属于自己的变量副本,其他线程无法访问自己内部的副本变量(每个Thread线程内部都有一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本ThreadLocal是一个弱引用。

1.ThreadLoca 是线程局部变量,在于每个访问该变量的线程,在线程内部都会初始化一个独立的变量副本,只有该线程可以访问【get() /set()】访问自己内部的副本变量,ThreadLocal实例通常声明为 private static。

2.线程在存活并且ThreadLocal实例可被访问时,每个线程隐含持有一个线程局部变量副本,当线程生命周期结束时,ThreadLocal的实例的副本跟着线程一起消失,被GC垃圾回收(除非存在对这些副本的其他引用)

ThreadLocal作用:确保在同一个线程下运作,调用的多个方法,他们可以共享同一份数据。

ThreadLocal的API

  • set(T value):将线程局部变量当前副本中的值设置为指定值

  • get():返回此线程局部变量当前副本中的值

  • initialValue():返回此线程局部变量当前副本中的初始值

  • remove():移除此线程局部变量当前副本中的值

ThreadLocal的set()

 ThreadLocal的get()

 ThreadLocal的initialValue()

 ThreadLocal的remove()

 ThreadLocalMap的Entry实体类

1.每个Thread线程内部都有一个Map(ThreadLocalMap)

2.Map集合里面存储Entry实体类:ThreadLocal 对象(key)和 线程的变量副本(value) ,   (key是 当前的线程ThreadLocal,value值 就是我们往ThreadLocal里面存的值)。

3.Thread线程内部Map是由ThreadLocal维护的,由ThreadLocal负责向Map获取和设置线程的变量值。

4.对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

内存泄露 和 内存溢出 的区别

区  别
内存泄露是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光(本应该被GC回收的无用对象没有被回收,导致的内存空间的浪费Java内存泄露根本原因是:长生命周期的对象持有短生命周期对象的引用,导致不能被GC回收。内存泄露严重时会导致内存溢出OutOfMemoryError(OOM)
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError

常见的内存溢出的OutOfMemoryError(OOM)异常情况:

        1.堆溢出(java.lang.OutOfMemoryError:java heap space)
        2.持久代溢出(java.lang.OutOfMemoryError: PermGen space)
        3.栈溢出(java.lang.StackOverflowError)
        4.OutOfMemoryError:unable to create native thread

1.如何避免出现内存泄露

        1.尽量少使用枚举

        2.尽量使用静态内部类而不是内部类

        3.尽量使用轻量级的数据结构

        4.养成关闭连接和注销监听器的习惯

        5.谨慎使用static关键字

        6.谨慎使用单例模式

2.出现内存溢出或内存泄露的解决方案

  1.修改JVM启动参数(-Xms,-Xmx),直接增加虚拟机内存。

  2.检查错误日志。

  3.使用内存查看工具查看内存使用情况(如jconsole)

  4.对代码进行仔细分析,找出可能发生内存溢出的位置。

  详细排查方案如下:

                1.检查对数据库查询中,是否有一次获得全部数据的查询。

                2.检查代码中是否有死循环或递归调用。

                3.检查是否有大循环重复产生新对象实体(大量创建新对象)。

                4.检查List、Map等集合对象是否有使用完后,未清除的问题(未被GC回收)。
                5.检查是否有大量的连接对象或监听器等未关闭。

 Java 对象的引用

 Java 中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和 虚引用。

强引用:一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象(强引用是造成 Java 内存泄漏的主要原因之 一)。

弱引用:JVM进行GC垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

1强引用new的对象,哪怕内存溢出也不会回收永不回收,Java 中默认引用
2软引用只有内存不足时才会回收内存溢出前回收
3弱引用每次GC垃圾回收都会回收下次GC时回收
4虚引用必须配合引用队列使用,一般用于追踪垃圾回收动作无法通过 PhantomReference 获取对象,作用是 GC 时返回一个通知

如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。

JVM如何找到需要回收的对象,方式有两种:

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,
  • 可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

为什么使用ThreadLocal弱引用?

如果发现entry的key为null,就会用新的entry覆盖掉旧的entry。

        每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。

        什么情况下key会等于null?就是ThreadLocal用完后没有remove,弱引用在下一次垃圾回收时会被自动清理,这个时候key就等于null了。然后通过上面这个代码,就能覆盖掉这些无用的垃圾对象,增加保障。

// ThreadLocal 本地线程

private static ThreadLocal<T> threadLocal= new ThreadLocal<T>() 

    /**
     * 向当前线程中存入用户数据
     *
     * @param user
     */
    public static void setUser(User user) {
        threadLocal.set(user);
    }

    /**
     * 从当前线程中获取用户数据
     *
     * @return
     */
    public static User getUser() {
        return threadLocal.get();
    }

    /**
     * 获取登陆用户的id
     *
     * @return
     */
    public static Long getUserId() {
        return threadLocal.get().getId();
    }
    /**
     * 使用完后移除threadLocal变量副本,让GC成功回收
     *
     * @return
     */
    public static void remove() {
        threadLocal.remove();
    }

ThreadLocal的两个主要的应用领域:

1.解决并发问题:

        解决并发问题的本质是:一种以空间换时间的思路,时间效率提升了,但是也存在着内存使用时的潜在溢出风险。

2.解决数据存储问题:

        数据存储问题主要指的是:系统中多个组件如何实现数据的交互和共享,而作为执行者的线程作为数据载体再适合不过了。虽然各种组件可以实现数据共享,但是数据在线程间是隔离的。

线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每个线程创建一个单独的变量副本,每个线程都可以改变自己的变量副本而不影响其它线程所对应的副本。

synchronized是以时间换空间,让不同的线程排队访问;

ThreadLocal是以空间换时间,为每个线程都设置了一份变量。

ThreadLocal和Synchonized区别:都用于解决多线程并发访问

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问,用于在多个线程间通信时能够获得数据共享。

而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值