一、谈谈你对ThreadLocal的理解
1、ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定
2、ThreadLocal适用于在多线程的情况下,可以实现传递数据,实现线程隔离
3、ThreadLocal基本API
1)new ThreadLocal(); ---创建ThreadLocal
2)set ---设置当前线程绑定的局部变量
3)get ---获取当前线程绑定的局部变量
4、ThreadLocal提供给我们每个线程缓存局部变量
二、哪些地方有使用ThreadLocal
1、spring事务模板类
2、Javaweb项目SpringMVC,获取http request对象
---tomcat接受请求,创建一个线程
---servlet框架处理请求
---SpringMVC对Servlet做了一层封装,封装了http request对象放入ThreadLocal
---自定义Aop拦截请求
---到控制器层---获取到ThreadLocal中的http request对象
最终都是同一个线程在处理(中间进过n多个不同的方法)
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
3、AOP、LCN分布式事务、分布式服务追踪框架源码
生成全局事务id放入ThreadLocal
4、设计模式
模板方法---模板类
5、AOP拦截请求
---AOP层缓存变量到ThreadLocal中
---控制层获取到该变量
6、分层架构
控制层 ---将变量缓存到ThreadLocal中
业务逻辑层 ---接口不传递变量,从ThreadLocal获取到该变量
DB层
三、ThreadLocal与Synchronized区别
1、Synchronized与ThreadLocal都可以实现多线程访问,保证线程安全的问题
2、Synchronized采用当多个线程竞争到同一个资源的时候,最终只能够有一个线程访问,采用时间换空间的方式,保证线程安全问题
3、ThreadLocal在每个线程中都有自己独立的局部变量,空间换时间,相互之间是隔离的
4、相比来说ThreadLocal效率比Synchronized效率高
1、在每个线程中都有自己独立的ThreadLocalMap对象,map对象中有Entry对象,Entry对象是key-value形式,Entry(ThreadLocal> k, Object v)
2、如果当前线程对应的ThreadLocalMap对象为空的情况下,则创建该ThreadLocalMap对象,并且赋值键值对
key为当前new ThreadLocal对象,value就是为object变量值
3、ThreadLocalMap生命周期与线程做绑定关联的
五、为什么线程缓存的是ThreadLocalMap对象,不是ThreadLocal对象
1、ThreadLocalMap可以存放n多个ThreadLocal对象
2、当前线程有可能缓存n多个ThreadLocal对象
3、每个ThreadLocal对象只能缓存一个变量值
六、ThreadLocal为何引发内存泄露问题
1、什么是内存泄露问题
内存泄露表示就是我们程序员申请了内存,但是该内存一直无法释放
2、什么是内存溢出
表示我申请内存时,发现申请内存不足,就会报错,内存溢出的问题
3、因为每个线程中都有自己独立的ThreadLocalMap对象,key为ThreadLocal,value为变量值
key为ThreadLocal作为Entry对象的key,是弱引用,当ThreadLocal指向null的时候,Entry对象中的key变为null,该对象一直无法被垃圾收集机制回收,一直占用到了系统内存,有可能会发生内存泄露的问题
七、如何防御ThreadLocal内存泄露问题
1、可以自己调用remove方法将不要的数据移除避免内存泄露的问题
2、每次在做set方法的时候,会清除之前的key为null
3、ThreadLocal对象为弱引用
static class Entry extends WeakReference> {
...
}
八、谈谈强、软、弱、虚引用的区别
1、强引用:当内存不足时,JVM开始进行GC(垃圾回收),对于强引用对象,就算是出现了OOM也不会对该对象进行回收,死都不回收
2、软引用:当系统内存充足时,不会回收;当系统内存不足时,它会被回收。软引用通常用在对内存敏感的程序中,比如高速缓存就用到软引用,内存够用时就保留,不够时就回收
3、弱引用:弱引用需要用到java.lang.ref.WeakReference类来实现,它比软引用的生存周期更短。对于只有弱引用的对象来说,只要有垃圾回收,不管JVM的内存空间够不够用,都会回收该对象占用的内存空间
4、虚引用:虚引用需要java.lang.ref.PhantomReference类来实现,顾名思义,虚引用就是形同虚设。与其他几种引用不同,虚引用并不会决定对象的生命周期
5、强引用demo
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就表明这个对象还活着,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,就是把一个对象赋值给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收掉的,即使该对象以后永远都不会被用到,也不会被JVM回收掉。因此,强引用是造成Java内存泄露的主要原因之一
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示的将强引用赋值为null,一般就认为可以被当做垃圾收集了(具体还得看垃圾回收策略)
public static void main(String[] args) {
//定义一个强引用
Object o1 = new Object();
//o2引用赋值
Object o2 = o1;
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(o2);
}
执行结果:
null
java.lang.Object@15db9742
6、软引用demo
public static void main(String[] args) {
Object o1 = new Object();
SoftReference
执行结果:
可以看到,OOM发生后,软引用对象被回收了
[GC (Allocation Failure) [PSYoungGen: 1024K->485K(1536K)] 1024K->737K(5632K), 0.0132098 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
o1:java.lang.Object@15db9742
软引用对象:java.lang.Object@15db9742
[GC (Allocation Failure) [PSYoungGen: 899K->501K(1536K)] 1151K->881K(5632K), 0.0004761 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 501K->501K(1536K)] 881K->913K(5632K), 0.0004866 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 501K->0K(1536K)] [ParOldGen: 412K->811K(4096K)] 913K->811K(5632K), [Metaspace: 2672K->2672K(1056768K)], 0.0039309 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 811K->811K(5632K), 0.0002087 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 811K->799K(4096K)] 811K->799K(5632K), [Metaspace: 2672K->2672K(1056768K)], 0.0042236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
------------------------------
o1:null
软引用对象:null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at webapp2_base.Test2.main(Test2.java:16)
Heap
PSYoungGen total 1536K, used 41K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 4% used [0x00000000ffe00000,0x00000000ffe0a538,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 4096K, used 799K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000)
object space 4096K, 19% used [0x00000000ffa00000,0x00000000ffac7e38,0x00000000ffe00000)
Metaspace used 2702K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 287K, capacity 386K, committed 512K, reserved 1048576K
7、弱引用demo
public static void main(String[] args) {
Object o1 = new Object();
WeakReference
执行结果:
可以看到GC后,弱引用对象被回收了
java.lang.Object@15db9742
java.lang.Object@15db9742
------------------------------
null
java.lang.Object@15db9742
------------------------------
null
null
九、ThreadLocal采用弱引用而不是强引用
1、如果key是为强引用,当我们现在将ThreadLocal的引用指向null,但是每个线程中有自己独立ThreadLocalMap还一直在继续持有该对象,但是我们ThreadLocal对象不会被回收,就会发生ThreadLocal内存泄露的问题
2、如果key是为弱引用,当我们现在将ThreadLocal的引用指向null,Entry中的key指向为null,但是下次调用set方法的时候,框架会根据判断如果key空的情况下,直接删除Entry,避免了Entry发生内存泄露的问题
3、不管是强引用还是弱引用都是会发生内存泄露的问题
4、但是最终根本的原因ThreadLocal内存泄露的问题,产生于ThreadLocalMap与我们当前线程的生命周期一样,如果没有手动删除的情况下,就有可能会发生内存泄露的问题
参考资料:
https://zhuanlan.zhihu.com/p/158033837