ThreadLocal
1.是什么
ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程自己的静态变量。
线程间交互的几种方式
- 互斥同步锁:Sync和ReentranLock
- 非阻塞同步:CAS和AtomicXXX变量
- 可见变量: volatile可见变量
- 本地存储: 线程ThreadLocal,多线程环境下数据的独立性
2.使用场景有哪些
场景一:管理连接对象 Mybatis的连接对象 解决当前线程的操作都是用同一个Connection,保证了事务
- 方案一:直接多线程获取连接,线程不安全,连接可能被其他线程关闭
- 方案二:每个线程独自场景连接,互补影响,问题:每个线程是复用的,都要频繁的创建和关闭连接,效率低
- 方案三:ThreadLocal变量存储
ThreadLocal在每个线程中保存一个对该变量的副本,线程间互不影响,性能也比较好
场景二:Session管理 解决对象层层传递的问题
Session管理场景中避免多重修改
方法多层堆栈调用,A->B->C-D-E,如果A中的某个变量,在E中使用,而不期望中间改变
3.原理是什么
首先找到当前线程ID,根据线程IDThreadID,获取对应的ThreadLocalMap对象,map中存放的是多个Entry,每个Entry对应一个ThreadLocal对象
ThreadLocal<String> a = new ThreadLocal<>();
ThreadLocal<String> b = new ThreadLocal<>();
b.set("valueB");
a.set("valueA");
- 线程类中有一个
ThreadLocalMap类型的变量threadLocals
- ThreadLocalMap的Entry实现继承了WeakReference<ThreadLocal<?>>
每一次set或者get的时候,都会将key为null的Entry清除掉
使用线程池,执行完成线程任务的时候要清理remove方法
整体结构
按照上面的代码示例,整体的map存储结构如下,
| Key | Value _下一层的Key | Value |
|---|---|---|
| threadId | ThreadLocalMap | |
| This = ThreadLocal对象 | value | |
| a | valueA | |
| b | valueB |
方法详解
set
ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。
Map中有一个静态内部类Entry,是WeakReference的子类使用Entry<key,Value> 来存储具体内容
key为ThreadLocal对应 value为存储的内容
如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
get
如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
initialValue
initialValue() 是 ThreadLocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 ThreadLocal 的初始值。
private T setInitialValue() {
T value = initialValue(); // 就是null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
remove
用来移除当前 ThreadLocal 对应的值。其实就是map的remove,用来释放Entry中的key
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
常见属性
没有实现Map接口,自定义Map
// 初始容量,必须是 2 的幂
private static final int INITIAL_CAPACITY = 16;
// 存储数据的哈希表
private Entry[] table;
// table 中已存储的条目数
private int size = 0;
// 表示一个阈值,当 table 中存储的对象达到该值时就会扩容
private int threshold;
// 设置 threshold 的值
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
为什么会内存泄漏
原因
假设Entry中的key没有使用弱引用,而是普通的强引用,当线程结束后,引用ThreadLoal的对象被回收后,由于还有其他线程,ThreadLocalMap中还存在着,其中有ThreadLocal(Key)-value(value)这样的引用,导致value无法回收掉,造成内存泄漏
ThreadLocal<String> b = new ThreadLocal<>();
b.set("");
b = null; // 内存泄漏
此时map中的接口
threadLocal中
key = b -> value = String // 由于threadlocal b的存在,执行value对象,导致value对象无法被清除
而key使用了弱引用,每次gc都会被清除掉,从而避免内存泄漏
ThreadLocalMap 已经考虑到这种情况,并且有一些防护措施:在调用 ThreadLocal 的 get(),set() 和 remove() 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为 null 的 value。这样可以降低内存泄漏发生的概率。所以我们在使用 ThreadLocal 的时候,每次用完 ThreadLocal 都调用 remove() 方法,清除数据,防止内存泄漏。
有哪些需要我注意
- 每一次set或者get的时候,都会将key为null的Entry清除掉,为了防止长时间没有set,get的调用,用完手动remove
- 使用线程池时,执行完成线程任务的时候要清理threadlocal变量,即调用remove方法,防止变量中的值异常
Java中的四种引用类型
- 强引用
一般对象,使用new 方法创建的
-
软引用
特点:内存不够的时候,gc会将其回收
场景:不重要内容的缓存(例如图片)
-
弱引用
特点:每次执行GC都会将其回收
场景:JVM中ThreadLocal里的Entry使用
-
虚引用
特点:随时可以清理
场景:JVM用于管理直接内存,在直接内存使用场景下,用于监控直接内存被回收
当直接内存清理的时候,会通过Queue进行通知,此时Queue不为空
new PhantomReference(new Object, Queue)