今天我们讲一下高频面试题,Threadlocal,他是JDK1.2就已经有了,他是为每一个使用该变量的线程提供独立的副本,可以做到线程间的数据隔离,每一个线程都可以访问各自内部的副本变量。
我们先看一下他里面的结构图,看着结构图再看源码,就容易理解多了,
看到Threadlocal的结构图,我们再看一下他常用的几个方法
protected T initialValue() {
return null;
}
这个方法就是Threadlocal初始化的方法,我们可初始化Threadlocal里面的值,当我们没有set操作的时候,我默认获取的就是这个初始化值,如果你没有重写这个方法返回就是源码中的null.
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //与当前线程绑定的ThreadLocalMap
if (map != null) //如果为null
map.set(this, value); //设置当前ThreadLocal为key,设置值value
else
createMap(t, value); //新创建ThreadLocalMap
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); //新创建ThreadLocalMap
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; //获取当前Entry数组
int len = tab.length; //获取数组的长度
int i = key.threadLocalHashCode & (len-1); //根据key的哈希值计算出数组下标
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) { //循环遍历Entry数组
ThreadLocal<?> k = e.get(); //获取Entry[i] 的key
if (k == key) { //如果k和当前的Thradlocal的key相等
e.value = value; //则覆盖之前的value
return;
}
if (k == null) { //如果当前的k为null
replaceStaleEntry(key, value, i); //清除为null的位置,使用新的数据占据位置
return;
}
}
tab[i] = new Entry(key, value); //最后也不相等,也不为空,就会重新建立一个Entry
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)//清除key为null的数据,并和阀值比较
rehash();
}
1.获取当前线程Thread.currentThread(). 2.根据当前线程获取与之关联的ThreadLocalMap数据结构. 3.如果map为null则进入第四步,否则进入第五步. 4.当map为null的时候创建一个ThreadLocalMap,用当前ThreadLocal实例作为key,将要存放的数据作为value,对应到ThreadLocalMap中则是创建了一个Entry 5.在map的set方法中遍历整个map的Entry,如果发现ThreadLocal相同,则替换成新的数据,set结束 6.在遍历的过程中,发现Entry中key为null,则会直接将其清除,并且使用新的的数据占用被逐出数据的位置,主要是防止内存泄露 7.创建新的entry,使用ThreadLocal作为Key,将要存放的数据作为value. 8.最后在根据ThreadlocalMap的大小和阀值比较,并再清理key为null的数据.
get用于返回当前线程在Threadlocal中的数据备份,当前线程的数据放在一个ThreadLoclaMap的数据结构中。
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取当前线程绑定的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//获取ThreadLocalMap的entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result; //返回ThreadLocalMap的entry的value
}
}
return setInitialValue();//初始化设置值
}
private T setInitialValue() {
T value = initialValue(); //初始化值
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //当前线程绑定的ThreadLocalMap
if (map != null)
map.set(this, value);//把当前的ThreadLocal设置为ThreadLocalMap的key,当前数据设置成value
else
createMap(t, value);//创建新的ThreadLocalMap
return value;//返回初始化的值,如果没有重写initialValue,默认返回null
}
1.首先获取当前线程Thread,currentThread()方法 2.根据Thread获取ThreadlocalMap,其中ThreadLocalMap与Thread关联,而我们存入的Threadlcoal中的数据实时上是存储在ThreadlocalMap的Entry中的 3.如果map已经被创建过,则以当前的Threadlocal作为key获取对应的Entry. 4.如果Entry不为null,则直接返回Entry的value的值,否则进入第五步 5.如果在第二步获取不到对应的ThreadLocalMap,则执行setInitialValue()方法. 6.在sentInitialValue()方法中首先通过执行initialValue()所获得的初始值, 7.根据当前线程Thread获取对应的ThreadlocalMap. 8.如果ThreadLocalMap不为null,则为map指定initalValue()所获得的初始值,实际上是在map.set(this,value)方法中new了一个新的Entry对象. 9.如果ThreadLocalMap为null(首次使用的时候),则创建一个ThreadLocalMap,并且与Thread对象的threadlocals属性相关联(这里发现ThreadLocalMap构造过程是一个Lazy的方式) 10,返回initialValue()重写的结果,当然这个结果在没有重写的时候,结果为null.
ThradLocalMap是一个类似于HashMap的数据结构,用于把线程放在ThreadLocal中的数据进行备份,ThreadLocalMap所有方法对外不可见,注意一点ThreadLocalMap中的存储数据的Entry,他是一个WeakReference类型的子类,之所以设计成WeakReference是为了能够在JVM发生垃圾回收的时候,自动回收防止内存溢出的情况出现,通过源码就很容易理解
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们先看一张图
对于ThreadLocal内存泄露,网上有很多说法,都说的很对,但是个人理解,有些说的有些模糊.
到此我们讲解完了ThreadLocal的实现原理以及内存泄露原因.