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设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程
*
*/