面试分析:你懂不懂threadlocal?
目录
ThreadLocal
应用场景
get与set方法
ThreadLocalMap
ThreadLocalMap.set
ThreadLocalMap.get
ThreadLocalMap.remove
线程隔离
内存泄漏
今天看一个面试题,threadlocal的原理
首先,要知道threadlocal是什么,如果都不知道这个是什么,那就也别看什么原理了.....
ThreadLocal
threadlocal 是线程的内部存储类,可以在指定线程内存储数据,且只有指定线程可以拿到数据
说白了,就是为了数据安全
不同的线程在使用 threadlocal 的时候,都会有自己的一个 threadlocalmap 对象,并且通过 threadlocal 进行管理
每个新线程都会被实例化一个 threadlocalmap,并且赋值给 threadLocals,如果已经存在了 threadLocals,那就直接复用
应用场景
那种作用域是线程,并且不同的线程还会有不同的副本的时候,就可以考虑用 threadlocal
当然,如果说业务处理逻辑强依赖与 副本变量,那可能就不大适合这玩意了
get与set方法
这一块我们可以直接看源码就行
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
可以看到,无论是 get 还是 set,都是用的 ThreadLocalMap 的方法
ThreadLocalMap
ThreadLocalMap 为每个 thread 都维护了一个数组 table,threadllcal 确定了一个数组下标,而这个下标是value存储的对应位置
从之前的 set 源码,我们可以得到,ThreadLocalMap 是一个延迟创建的结构,也就是说,至少要放置一个内容时才会创建
而通过创建 ThreadLocalMap 的源码,我们可以得到,ThreadLocalMap 初始化时创建了默认长度是16的 Entry 数组。通过 hashCode 与 length 位运算确定索引值 i
ThreadLocalMap.set
我们来看看 threadLocalMap 的 set 方法
ThreadLocalMap.get
get 方法没有什么可看的,就直接取就完事了
ThreadLocalMap.remove
我们再看看 remove 方法
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
可以看到,也没什么东西
线程隔离
threadlocal 拥有线程隔离特性,只有线程内可以访问
类比 synchronized,它是通过线程等待来解决访问冲突问题
而 threadlocal 是通过空间存储来解决访问冲突问题
内存泄漏
注意,threadlocal 有一个地方需要注意,那就是内存泄漏的问题,使用完,一定要 remove,否则永远清除不掉,时间长了就会出问题了