ThreadLocal的作用是做数据隔离,存储的变量只属于当前线程,相当于当前线程的局部变量,多线程环境下,不会被别的线程访问与修改。常用于存储线程私有成员变量、上下文,和用于同一线程,不同层级方法间传参等。JDK 1.8 中的 ThreadLocal 共741行代码,其中包含3个成员变量,13个成员方法和两个内部类。 我们先来看下核心原理,再来详细看下源码。
我们可以带着问题去学习这部分内容,希望学习完后,能回答这些问题:
这个要从java.lang.Thread类说起,每个Thread对象中都拥有一个ThreadLocalMap(ThreadLocal的内部类)的成员变量。ThreadLocalMap内部又拥有一个Entry数组,每个Entry是一个键值对,key是ThreadLocal本身,value是ThreadLocal的泛型值。 (这里Thread类虽然有ThreadLocalMap成员变量,但没有get(),set(),remove()等增删改查的方法,其实就是通过ThreadLocal来操作的。)
例如我们每一次请求,就是一个线程,然后一个线程里就有且只有一个ThreadLocalMap,然后我们的业务里可能new了好几个ThreadLocal对象,存了几个ThreadLocal的值,这些就存在Entry数组中,然后,我们根据当前线程和当前ThreadLocal就能找到唯一的
结构图如下:
核心源码如下:
// java.lang.Thread类里持有ThreadLocalMap的引用
public class Thread implements Runnable {
... ...
ThreadLocal.ThreadLocalMap threadLocals = null;
... ...
}
// java.lang.ThreadLocal有内部静态类ThreadLocalMap,主要提供给Thread类使用
public class ThreadLocal {
... ...
static class ThreadLocalMap {
// 存线程私有变量
private Entry[] table;
// ThreadLocalMap内部有Entry类,Entry的key是ThreadLocal本身,value是泛型值
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
}
... ...
}
这里介绍一下ThreadLocal的成员变量。
这里介绍一下ThreadLocal的成员方法。
public ThreadLocal() {
}
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 由子类提供实现。
// protected
protected T initialValue() {
return null;
}
public static ThreadLocal withInitial(Supplier extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public T get() {
// 获取当前线程,这里的currentThread()是个native方法
Thread t = Thread.currentThread();
// 获取当前线程对应的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 若获取到了。则获取此ThreadLocalMap下的entry对象,若entry也获取到了,那么直接获取entry对应的value返回即可
if (map != null) {
// 获取此ThreadLocalMap下的entry对象,把当前ThreadLocal当参数传进去
ThreadLocalMap.Entry e = map.getEntry(this);
// 若entry也获取到了
if (e != null) {
@SuppressWarnings("unchecked")
// 直接获取entry对应的value返回
T result = (T)e.value;
return result;
}
}
// 若没获取到ThreadLocalMap或没获取到Entry,则设置初始值
// 初始值方法是延迟加载
return setInitialValue();
}
boolean isPresent() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
return map != null && map.getEntry(this) != null;
}
// 设置初始值
private T setInitialValue() {
// 调用初始值方法,由子类提供。
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取map
ThreadLocalMap map = getMap(t);
// 获取到了
if (map != null)
// set
map.set(this, value);
else
// 没获取到。创建map并赋值
createMap(t, value);
// 返回初始值。
return value;
}
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
// 若当前线程有对应的ThreadLocalMap实例,则将当前ThreadLocal对象作为key,value做为值存到ThreadLocalMap的entry里。
if (map != null)
map.set(this, value);
else
// 若当前线程没有对应的ThreadLocalMap实例,则创建ThreadLocalMap,并将此线程与之绑定
createMap(t, value);
}
public void remove() {
// 获取当前线程的ThreadLocalMap对象,并将其移除。
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap构造器。
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// new了一个ThreadLocalMap的内部类Entry,且将key和value传入。
// key是ThreadLocal对象。
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
这里介绍一下ThreadLocal的内部类。
static final class SuppliedThreadLocal extends ThreadLocal {
private final Supplier extends T> supplier;
SuppliedThreadLocal(Supplier extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
static class ThreadLocalMap {
/**
* ThreadLocalMap 里数组里具体存的值
*/
static class Entry extends WeakReference> {
/**
* 与当前ThreadLocal 对应的值
*/
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量必须是2的幂次数,当前默认为16
*/
private static final int INITIAL_CAPACITY = 16;
/**
* ThreadLocalMap 里的Entry[] 数组,长度必须为2的幂次数
*/
private Entry[] table;
/**
* 当前 Entry[] table 的长度
*/
private int size = 0;
/**
* 阈值,超过后需扩容
*/
private int threshold; // Default to 0
/**
* 设置阈值为长度的 三分之二
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* I对len取模
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 获取前一个索引值
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构造方法,懒加载的
*/
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
// 根据初始容量,初始化表
table = new Entry[INITIAL_CAPACITY];
// 获取当前哈希值后,对len进行取模,确定索引位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
// 初始化长度,和阈值
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* 从给定父映射创建新映射
* */
private ThreadLocalMap(ThreadLocalMap parentMap) {
// 初始化参数
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal
相信看了上面的源码,文章开头的部分问题,你已经有了自己的答案,接下来我们再对一下答案。
1. ThreadLocal能不能代替Synchronized?和Synchronized的区别是什么?
答:ThreadLocal肯定不能代替Synchronized,ThreadLocal只是让变量变成了完全私有化,别的线程是无法访问的。而Synchronized除了解决线程冲突外,更重要的是,可以使变量被所有线程访问和修改。
2.Thread、ThreadLocal、ThreadLocalMap的关系是怎么样的?
答:关系有点绕,Thread类中包括ThreadLocalMap成员变量,ThreadLocalMap是ThreadLocal的内部类,ThreadLocalMap有Entry数组,Entry实体是键值对,其中key即是ThreadLocal类型。
3 存储在jvm的堆还是栈中?
答:很会人会觉得变量变成线程私有了,就一定是存在虚拟机栈中的,其实不是,我们私有变量是存在Thread对象里的,而对象都是存在堆里的,所以ThreadLocal的实例和他的值都是存在堆上的。
4. ThreadLocal会导致内存泄漏吗,为什么?
答:这个要从两方面分析,即ThreadLocalMap.Entry的key和value值分别讲:key直接是交给了父类处理super(key),父类是个弱引用,所以key完全不存在内存泄漏问题,value是个强引用,如果线程终止了,也会被GC干掉,但有时线程是不会被终止的,比如线程池里的核心线程,此时引用链就变成了:Thread->ThreadLocalMap->Entry(key为null)->value
,由于value和Thread还存在链路关系,还是可达的,所以不会被回收,这样越来越多的垃圾对象产生却无法回收,最终可能导致OOM,当然解决办法也简单,用完私有变量后使用remove()方法即可,它会删除所有value值。
5. 为什么用Entry数组而不是Entry对象?
答:在同一个线程里,我们可能需要多个线程私有变量,所以需要数组。
6. ThreadLocal里的对象一定是线程安全的吗?
答:不一定,因为ThreadLocal.set()进去的对象,可能本身可能就是可供多个线程访问的,比如static对象,这样是没办法保存线程安全的。
7. ThreadLocalMap只用单纯的数组存值吗?如果出现哈希冲突怎么存值?
答:ThreadLocalMap是只用数组存Entry值,如果 i 位置出现哈希冲突,则存在 i + 1处,如果 i + 1 也不为空,则依次往下顺延,直到找到空位为止。