一、介绍
ThreadLocal叫做线程本地变量,会给每个线程都分配一个属于自己的本地变量,用于线程变量隔离,保证线程安全
每个线程内部都会有自己的一个ThreadLocalMap,然后key是ThreadLocal,Value是我们自己设定的值,注意Entry中Key是弱引用,弱引用是发现就回收,继承了WeakReference这个,但是V不是,所以这里可能会产生
二、结构
简图
Thread.class
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
然后是我们的ThreadLocalMap
static class ThreadLocalMap {
//因为是map,所以肯定是KV结构
static class Entry extends WeakReference<java.lang.ThreadLocal<?>> {
Object value;
Entry(java.lang.ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//初始默认大小
private static final int INITIAL_CAPACITY = 16;
//Entry桶
private ThreadLocalMap.Entry[] table;
//size数量
private int size = 0;
//阈值
private int threshold; // Default to 0
//
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
}
重要方法
在ThreadLocal中,有这几个重要的方法,
- get()
- set()
- remove()
- initialValue()
get()方法
public T get() {
//获取到当前线程id
Thread t = Thread.currentThread();
//获取到当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果不为空
if (map != null) {
//通过ThreadLocal作为Key来查询对应的值,注意getEntry方法
ThreadLocalMap.Entry e = map.getEntry(this);
//如果这个值不是空的
if (e != null) {
//强转
T result = (T)e.value;
return result;
}
}
//如果map是空的或者获取到的值是空的,需要进行初始化或者设置初始值
return setInitialValue();
}
getEntry()
private ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
ThreadLocalMap.Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocalMap.Entry e) {
ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
//这里也就是不断++,访问该处的entry,因为在ThreadLocalMap中出现的hash冲突,是依赖于++解决的,
//而不是像HashMap那种链地址法
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
//这里是我们的i+1
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
setInitialValue()
private T setInitialValue() {
//这个是我们需要自己定义的代码
T value = initialValue();
//获取到当前线程id
Thread t = Thread.currentThread();
//获取到线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//map不为空,只需要赋初值
if (map != null)
map.set(this, value);
//map为空,需要继续宁create来创建一个ThreadLocalMap
else
createMap(t, value);
return value;
}
createMap()
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set()方法
public void set(T value) {
//获取到线程ID
Thread t = Thread.currentThread();
//获取到Map
ThreadLocalMap map = getMap(t);
if (map != null)
//调用map.set进行设置
map.set(this, value);
else
//创建一个线程
createMap(t, value);
}
ThreadLocalMap # set()
private void set(ThreadLocal<?> key, Object value) {
//获取到我们的Entry桶
ThreadLocalMap.Entry[] tab = table;
//获取长度
int len = tab.length;
//先获取到我们的索引
int i = key.threadLocalHashCode & (len-1);
//这里会有一个nextIndex来记录我们的下一个索引,需要根据这个nextInde来进行循环的下一个位置
//这里如果计算出的key不相等,就会将i++,然后判断i++处的key是不是空的,这里是利用移位法来解决的hash冲突
//而不是像hashMap那种链地址法
//for(entry=tab[index] ; entry!=null ; e=nextIndex)
for (ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key相等
if (k == key) {
//直接设置
e.value = value;
return;
}
//key为空
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//tab[i]是空的,直接new一个Entry放上去
tab[i] = new ThreadLocalMap.Entry(key, value);
//size++
int sz = ++size;
//判断是不是需要进行扩容,扩容需要判断size是否大于0.75*threadhold
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void resize() {
ThreadLocalMap.Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
//构建一个两倍长度的数组
ThreadLocalMap.Entry[] newTab = new ThreadLocalMap.Entry[newLen];
int count = 0;
//遍历
for (int j = 0; j < oldLen; ++j) {
ThreadLocalMap.Entry e = oldTab[j];
//如果下标有值
if (e != null) {
//获取到K
ThreadLocal<?> k = e.get();
//K是空的,想想弱引用,但上面的Entry却不是空的
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
//计算下标
h = nextIndex(h, newLen);
//直接设置
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//调用ThreadLocalMap.remove()方法
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
//计算下标
int i = key.threadLocalHashCode & (len-1);
//进行遍历
for (ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//如果key相等
if (e.get() == key) {
//进行清除
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
ThreadLocalMap.Entry e;
int i;
//遍历
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//如果key是空的,全部置为空
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
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;
}
nextIndex
private static int nextIndex(int i, int len) {
//返回i+1
return ((i + 1 < len) ? i + 1 : 0);
}
如何防止使用ThreadLocal的时候出现内存泄漏
1、每次使用完ThreadLocal都调用它的remove()方法清除数据
2、将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉