ThreadLocal线程局部变量学习

ThreadLocal 线程的本地变量

package com.mileage.controller;

import com.mileage.domian.AddressData;

/**
 * @ClassName HelloWorld
 * @Description ThreadLocal 内存泄漏问题,因为ThreadLocal是弱引用
 *              T当hreadLocal为null的时候,但是此时我们的ThreadLocalMap生命周期和Thread的一样,
 *              它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏
 *              解决方法调用remove()方法
 *              (1)每个Thread线程内部都有一个Map(ThreadLocalMap)
 *              (2)map里面存储ThreadLocal对象(引用key)和线程的变量副本
 *              (3)Thread内部Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值
 *              (4)对于不用的线程,每次获取副本值,别的线程并不能获取当前线程的副本值,形成了副本的隔离
 * 内存泄漏: 就是不再会被使用的对象或者占用变量的内存不能被回收,就是内存泄漏(内存堆积,迟早会被占用满)
 *              强引用: 使用普遍的对象引用,一个对象具备强引用,不会被gc回收,当内存空间不足
 *                      java虚拟机宁愿抛出OOM,使程序异常终止,也不回收这种对象
 *                      如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象
 *              软引用:  JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
 *                      在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用
 *                      如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,
 *                      就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用
 *                      注意事项:
 *                          软引用对象是在jvm内存不够的时候才会被回收,
 *                          我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收
 *                      举个例子:
 *                          String str = new String("abc"); //强引用
 *                          SoftReference<String> softReference = new SoftReference<String>(new String("str"));//软引用
 *              弱引用:  只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,
 *                      一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
 *                      不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象
 *              虚引用: 虚引用主要用于检测对象是否已经从内存中删除
 * @Version 1.0
 */
@SuppressWarnings("all")
public class HelloWorld {
    public static void main(String[] args) {
        InheritableThreadLocal<String> inheriLocal = new InheritableThreadLocal();
        inheriLocal.set("inheriLocal");
        final ThreadLocal<String> result = new ThreadLocal<>();
        result.set("PizzaCat");
        AddressData addressData = new AddressData();
        addressData.setProvince("American");
        //线程内部存储类,可以在指定线程内存储数据,数据存储之后,只有指定线程才能获取数据
        //key就是当前线程,value是要保存的数据
        final ThreadLocal<AddressData> threadLocal= new ThreadLocal<>();
        //线程1
        new Thread(() -> {
            threadLocal.set(addressData);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+threadLocal.get().getProvince());
            //获取主线程的数据
            System.out.println("主线设置变量数据:"+result.get()+";inheriLocal:"+inheriLocal.get());

            threadLocal.remove();
        }).start();
        //线程2
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+threadLocal.get());
        }).start();
        System.out.println("-----------------------------------------");
        inheriLocal.remove();
        System.out.println("-----------+++++++++++-----------");
        User user = new User("jack");
        new Service1().service1(user);
    }
}

class User {
    String name;
    public User(String name){
        this.name = name;
    }
}

class UserContextHolder{
    public static final ThreadLocal<User> holder = new ThreadLocal<>();
}
class Service1 {
    public void service1(User user){
        //给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}
class Service2{
    public void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2拿到用户数据:"+user.name);
        new Service3().service3();
    }
}

class Service3{
    public void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3拿到的用户:"+user.name);
        //在整个流程执行完毕后,一定要执行remove
        UserContextHolder.holder.remove();
    }
}
内存泄漏产生原因:
	由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。
    但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。
    因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

解决内存泄漏

(1)每次使用完ThreadLocal都调用它的remove()方法清除数据
(2)将ThreadLocal设置为private static 的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉

在这里插入图片描述

			ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链
/**
 * ThreadLocal 和 static 的区别
 * 举个例子: ThreadLocal 我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数
 *          我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡
 *          那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路)
 *          我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。
 *          这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的
 *          static 设置为全局变量: 你可以将公交卡设置为全局变量啊,
 *                               这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?
 *                               大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),
 *                               这样不就乱套了嘛。现在明白了吧?
 *          设计成单例的话会出现这样的情况:一个时间点只能有一个人使用公交卡,其他人只能等着                     
 * 这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程
 *
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值