ThreadLocal线程局部变量
- ThreadLocal线程局部变量
- ThreadLocal
- 基本方法
- set方法
- get方法
- remove方法
- 内存泄漏
ThreadLocal线程局部变量
ThreadLocal
共享数据是并发数据最核心的问题之一,对于继承了Thread或者实现Runnable接口的对象来说尤其重要
如果对象是实现了Runnable接口,那传入的参数将被多个线程共享,任意线程修改都还会影响到其他线程,会产生一些安全隐患
在某些情况某些属性不需要线程之间共享,java并发Api则为我们提供了线程局部变量ThreadLocal,极大的提供了方便
基本方法
set方法
ThreadLocal源码中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);
}
- set方法会判断map是否存在,否则创建一个map对象,类型是ThreadLocalMap
- createMap方法会创建一个新的ThreadLocalMap对象,同时当前线程的threadLocals变量引用这个map对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- getMap方法获取的就是 t.threadLocals变量的内容
- map.set方法是核心逻辑,底层使用Entry数组,根据hash值计算数组下标,然后根据下标获取是否
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
值得一提的是,由于弱引用导致的key为null的情况,会调用replaceStaleEntry方法,此方法会设置key
是null,同时把value值为null,供GC垃圾回收
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
上面提到的ThreadLocalMap使用Entry数组存放元素,可以看到Entry是实现了WeakReference,其中ThreadLocal对象是弱引用key值
这里也是出现内存泄露的主要原因,当业务代码用完ThreadLocal对象,ThreadLocal被回收时,由于没有强引用关联ThreadLocal,只有一个ThreadLocalMap关联弱引用的ThreadLocal,那么GC回收时该对象将会被回收,但是由于线程没有被销毁,始终强引用ThreadLocalMap对象,则value内容不会被回收,此时value没有任何用处但是也无法回收
get方法
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();
}
根据当前ThreadLocal对象获取ThreadLocalMap.Entry, 取到值返回。
如果没有获取到,则进行初始化方法,基本和set方法一致
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap的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;
}
}
}
根据hashcode定位到元素位置,之后进行clear方法
内存泄漏
刚刚上面也提到了,由于Entry的key使用的弱引用,当业务代码使用完之后,没有强引用在引用ThreadLocal对象,那么GC将要回收这个只有ThreadLocalMap引用的弱引用ThreadLocal对象,导致ThreadLocalMap的key为null,但是value是强引用无法被回收,导致内存泄漏
本文如有任何问题,请留言指教,以免误导他人