ThreadLocal
# 1 弱引用
# 1.1 为什么使用弱引用?
考虑下面的场景:
有一个Product类代表一种产品,这个类被设计为不可扩展的,此时我们想要为每个产品增加一个编号。
一种解决方案是使用 HashMap<Product, Integer>
。
于是问题来了,如果我们已经不再需要一个Product对象存在于内存中(比如已经卖出了这件产品),假设指向它的引用为productA,我们这时会给productA赋值为null,然而这时productA过去指向的Product对象并不会被回收,因为它显然还被HashMap引用着。
所以这种情况下,我们想要真正的回收一个Product对象,仅仅把它的强引用赋值为null是不够的,还要把相应的条目从HashMap中移除。
显然 从HashMap中移除不再需要的条目
这个工作我们不想自己完成,我们希望告诉垃圾收集器:在只有HashMap中的key在引用着Product对象的情况下,就可以回收相应Product对象了。
根据前面弱引用的定义,使用弱引用能帮助我们达成这个目的。我们只需要用一个指向Product对象的弱引用对象来作为HashMap中的key就可以了。
# 1.2 如何使用弱引用?
拿上面介绍的场景举例,我们使用一个指向Product对象的弱引用对象来作为HashMap的key,只需这样定义这个弱引用对象:
Product productA = new Product(...);
WeakReference<Product> weakProductA = new WeakReference<>(productA);
若引用对象weakProductA就指向了Product对象productA。我们怎么通过weakProduct获取它所指向的Product对象productA呢?很简单,只需要下面这句代码:
Product product = weakProductA.get();
# 1.3 什么是弱引用?
Java中的弱引用具体指的是 java.lang.ref.WeakReference<T>
类,官方文档对它做的说明:
弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收。弱引用最常见的用途是实现规范映射(canonicalizing mappings,比如哈希表)。
假设垃圾收集器在某个时间点决定一个对象是弱可达的(weakly reachable)(也就是说当前指向它的全都是弱引用),这时垃圾收集器会清除所有指向该对象的弱引用,然后把这个弱可达对象标记为可终结(finalizable)的,这样它随后就会被回收。与此同时或稍后,垃圾收集器会把那些刚清除的弱引用放入创建弱引用对象时所指定的引用队列(Reference Queue)中。
# 1.4 强引用、软引用、虚引用
实际上,Java中存在四种引用,它们由强到弱依次是:强引用、软引用、弱引用、虚引用。下面我们简单介绍下除弱引用外的其他三种引用:
强引用(Strong Reference):通常我们通过new来创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不被回收。
软引用(Soft Reference):软引用和弱引用的区别在于,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收,而软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些。
虚引用(Phantom Reference):虚引用是Java中最弱的引用,那么它弱到什么程度呢?它是如此脆弱以至于我们通过虚引用甚至无法获取到被引用的对象,虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。
# 2 什么是ThreadLocal?
ThreadLocal可以理解为线程本地变量。它会在每个线程都创建一个副本,各线程访问自己副本变量就可以,做到了线程之间的互相隔离,相比于synchronized这些锁来说,就是用空间来换时间。
ThreadLocal有一个静态内部类ThreadLocalMap,TreadLocalMap又包含一个Entry数组,Entry本身是一个弱引用,它的key指向ThreadLocal的弱引用,Entry具备了保存key value键值对的能力。
ThreadLocal实例通常来说是private static类型的,用于关联线程和线程上下文。
Thread跟ThreadLocal什么关系:
Thread和ThreadLocal是绑定的, ThreadLocal依赖于Thread去执行, Thread将需要隔离的数据存放到ThreadLocal(准确的讲是ThreadLocalMap)中, 来实现多线程处理。
# 3 Spring如何处理bean多线程下的并发问题?
ThreadLocal天生就是为解决相同变量的访问冲突问题, 所以这个对于spring的默认单例bean的多线程访问是一个完美的解决方案。
Spring用了ThreadLocal来处理多线程下相同变量并发的线程安全问题。
# 4 Spring如何保证数据库事务在同一个连接下执行?
DataSourceTransactionManager 是spring的数据源事务管理器, 它会在你调用getConnection()的时候从数据库连接池中获取一个connection, 然后将其与ThreadLocal绑定, 事务完成后解除绑定。这样就保证了事务在同一连接下完成。
# 5 Thread,ThreadLocalMap,ThreadLocal三者的逻辑关系?
1.Thread实例和ThreadLocalMap实例是一对一的关系
2.ThreadLocalMap实例和ThreadLocal实例是一对多的关系。
# 6 ThreadLocalMap底层结构是什么?
ThreadLocal中的map数据结构和HashMap不同,它并没有实现Map接口,也不像HashMap一样通过链表来解决hash冲突!
进阶问题:那么ThreadLocal是如何解决hash冲突呢?
答:采用的开放地址法,不是hashmap的链地址法!
ThreadLocal是采用数组来存储的。 ThreadLocalMap在存储的时候会给每一个ThreadLocal对象一个threadLocalHashCode,在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len-1)。
# 7 ThreadLocal为什么可能发生内存泄漏?
当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中(弱引用对象)。
这就导致了一个问题:ThreadLocal在没有外部强引用时,发生GC时key会被回收,如果创建ThreadLocal的线程一直持续运行(线程池的情况,线程跑完后会回到线程池,并不会销毁),那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
# 8 ThreadLocal如何避免内存泄露?
使用完ThreadLocal之后,调用 remove
方法。