ThreadLocal
ThreadLocal 叫做线程变量,意思是 TheadLocal 中填充的变量属于当前线程,该变量对于其他线程来说是隔离的。即 提供线程内的局部变量,不同的线程之间不会相互干扰。
总结:
1、线程并发
2、传递数据
3、线程隔离
ThreadLocal 和 Synchronized的区别
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是:
- synchronized同步机制采用 “时间换空间” 的方式,只提供了一份变量,让不同的线程排队访问。
- ThreadLocal采用 “空间换时间” 的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而互不干扰。
ThreadLocal底层原理
在 JDK1.8 中,每个 Thread 维护一个 ThreadLocalMap,这个 map 的 Key 是ThreadLocal的实例本身,value 是要存储的值。
具体的过程是这样的:
- 每个 Thread 的线程内部都有一个Map对象(ThreadLocalMap)
- Map里面存储 ThreadLocal 对象(key)和线程的变量副本(value)
- Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。
这样设计的好处
① 之前的存储数量是由 Thread 的数量决定的,现在是由 ThreadLocal 的数量决定。在实际应用中,ThreadLocal 的数量往往少于 Thread 的数量。
② 当 Thread 销毁后,对应的 ThreadLocalMap 也会随之销毁,能减少内存的使用。
常用方法
方法声明 | 描述 |
---|---|
ThreadLocal() | 创建ThreadLocal对象 |
.set(T value) | 设置当前线程绑定的局部变量 |
.get() | 获取当前线程绑定的局部变量 |
.remove() | 移除当前线程绑定的局部变量 |
ThreadLocal的核心方法源码
Set方法
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程对象
ThreadLocalMap map = getMap(t); //获取此线程对象中的 ThreadLocalMap对象
//判断map是否存在
if (map != null)
map.set(this, value); //存在则调用map.set设置这个entry
else
createMap(t, value); //不存在则给该线程创建map,并设置初始值
}
- 首先获取当前线程,并根据当前线程获取一个Map
- 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)
- 如果Map为空,则给该线程创建 Map,并设置初始值
get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//判断map是否存在
if (map != null) {
//以当前的 ThreadLocal 为key,获取存储的实体entry
ThreadLocalMap.Entry e = map.getEntry(this);
//如果实体不为空,则获取对应的value值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
/*
初始化,有两种情况会执行当前代码:
1、map不存在,表示此线程没有维护的 ThreadLocalMap对象
2、map存在,但是没有当前 ThreadLocal 作为key关联的value
*/
return setInitialValue();
}
先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。
remove方法
public void remove() {
//获取当前线程维护的 ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
//移除当前 ThreadLocal对象 对应的entry
if (m != null)
m.remove(this);
}
ThreadLocalMap源码分析
基本结构
ThreadLocalMap 是 ThreadLocal 的内部类,没有实现 Map 接口,用独立的方式实现了 Map 的功能,其内部的 Entry 也是独立实现。
成员变量:
private static final int INITIAL_CAPACITY = 16; //初始容量,必须是2的次幂
private Entry[] table; //存放数据的table
private int size = 0; //数组里面Entry的个数
private int threshold; //进行扩容的阈值
存储结构(Entry):
Entry继承 “弱引用” ,并且用 ThreadLocal
对象作为 key。
如果 key 为 null,意味着key不再被引用,这时候entry可用从table中清除。
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal内存泄漏的根源
由于 ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以它的生命周期和 Thread 一样长,如果没有手动删除对应的 key 就会导致内存泄漏。
为什么使用弱引用
要避免内存泄漏有两种方式:
① 使用完ThreadLocal,调用 remove 方法删除对应的 key。
② 使用完ThreadLocal,当前 Thread 也随之运行结束。
相对于第一种方式,第二种方式显然不好控制,特别是使用线程池的时候,线程结束是不会销毁的。
弱引用只针对 Entry 里的 Key,每个 Key 都弱引用的指向 ThreadLocal 实例。所以把 ThreadLocal 实例置为 NULL 后,没有任何强引用指向 ThreadLocal 实例,所以就可以顺利的被 GC 回收。
如果每个 Key 是强引用的指向 ThreadLocal 实例,那么这个 ThreadLocal 就会因为和 Entry 存在强引用无法被回收,造成线程泄漏,直到线程结束。
因此 ThreadLocal 如何防止内存泄漏:使用完后,记得 remove()
hash冲突的解决
ThreadLocalMap 使用 线性探测法 来解决哈希冲突。
该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。