ThreadLocal在平时工作中还是比较常见的类,作为一个解决多线程问题的一个途径,还是值得去学习下的.
常见应用场景包括数据库连接、Session管理等.
它的工作原理,简单的说就是ThreadLocal为每个线程创建一个本地副本变量机制,实现与其他线程的隔离.
源码解析
1.get 方法
get方法流程如图:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// ThreadLocalMap是存储线程副本的关键点
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取Entry对象, 实际保存的数据在Entry中
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 初始化ThreadLocalMap 对象
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 创建ThreadLocalMap
createMap(t, value);
return value;
}
2.set方法
set方法直接设置数据,ThreadLocalMap 不存在时进行初始化并设值.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 创建ThreadLocalMap
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 计算tab索引值
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;
}
// 由于key是弱引用,key为null之后要释放对应的value值,防止内存泄漏
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// tab中对应的key不存在,存入tab中
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
3.remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
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();
// 清除过期Entry
expungeStaleEntry(i);
return;
}
}
}
4.replaceStaleEntry
replaceStaleEntry是删除过期的Entry,并添加新Entry的方法
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
// 向前查找到第一个过期Entry
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 向后查找
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果查找到相同的key值
if (k == key) {
e.value = value;
// 交换过期entry位置
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 如果之前向前查找中未发现过期Entry,则以当前位置进行清除
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 向前查找未发现过期Entry,而此时找到过期Entry,则以当前位置进行清除
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 清除当前过期关联,将新数据关联到该索引
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
5.expungeStaleEntry方法
expungeStaleEntry方法执行删除过期Entry操作
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 删除过期Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
// tab实际数据数减1
size--;
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 遇到过期的进行清除
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//重新计算hash索引
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
6.cleanSomeSlots 方法
cleanSomeSlots 还是执行清除过期Entry的操作,这里主要做再次扫描清除Entry操作
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
为何ThreadLocal存在内存泄漏?
ThreadLocal实际维护副本变量是通过ThreadLocalMap类来实现.相关定义源码如下:
static class ThreadLocalMap {
// Entry 对key进行了弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
在进行GC操作后k会被回收,而此时没有v仍保持引用.在特定情况下大量的v堆积,造成内存泄漏.
为何k不采用强引用?
k对应者ThreadLocal实例,当设置业务中实例=null,准备销毁,而threadLocalMap中的Entry仍强引用实例,
根据GC的可达性分析,仍为可达,造成ThreadLocal实例无法进行回收.
使用弱引用?
虽然存在泄漏内存的可能,但是根据之前源码的get/set/remove都对失效Entry进行了处理,可以极大避免问题出现可能.