定义:
创建线程局部变量的类
特点:
一般情况下,创建的变量可以被任何一个线程访问并且修改,但是使用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);
}
首先获取当前线程引用,currentcurrentThread()为本地方法,然后根据当前工作线程的引用获取ThreadLocalMap对象,getMap方法比较简单,返回Thread类的成员变量threadLocals,即ThreadLocalMap对象。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
返回到set方法继续看,如果获取到的ThreadLocalMap对象为空的话则通过createMap方法创建ThreadLocalMap对象,通过直接对threadLocals实例化来创建,代码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以发现ThreadLocalMap对象传入的key值为this,即代表当前ThreadLocal对象,因此每一个都会有一个ThreadLocalMap对象,以当前的ThreadLocal对象为key值存储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)
return (T)e.value;
}
return setInitialValue();
}
通过getMap获取到当前线程的ThreadLocalMap对象,然后如果map不为空再以当前ThreadLocal对象为key获取value,从而保证了数据隔离。如果getMap返回null值则需要进行初始化操作,setInitialValue源码和set方法类似,只不过value值是默认值,如下:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// 默认会返回null值,可以通过重写来自定义返回值。
protected T initialValue() {
return null;
}
哈希策略
在createMap方法内部会通过构造方法实例化一个ThreadLocalMap对象,下面看一下Map内部的存储逻辑:
//这里新建了一个Entry对象的数据,并且将当前值设置进去。
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
这里注意构造方法第三行,获取索引的哈希策略,相关的代码以及注释如下:
private final int threadLocalHashCode = nextHashCode();
// 用于计算hashCode,从零开始,在所有的ThreadLocal间共享。
private static AtomicInteger nextHashCode = new AtomicInteger();
// 由于初始长度为16,每次扩容为以前的两倍,即长度都是2的n次方,使用这个常量尽可能的分布均匀。
private static final int HASH_INCREMENT = 0x61c88647;
// 返回下一个散列码。
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
假设我们set的时候已经创建好了ThreadLocalMap对象,进入ThreadLocalMap对象的set方法看看:
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;//初始为16扩容乘2,所以一直为2的n次幂
/** 根据key的hash值找到数组的存储位置,使用0x61c88647与上2的n次幂-1,二进制的低位全为1,
结果就是0x61c88647的低位,可以验证一下,分布均匀的。
*/
int i = key.threadLocalHashCode & (len-1);
/** 如果进入for循环,说明出现了碰撞冲突,用到开放寻址法,线性探测
解决方案是通过+1的方式逐渐遍历数组找到空位
*/
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) { // 如果key值相同则直接替换value
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();
}
// nextIndex方法如下
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
关于内存泄漏
多数观点:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法。
但是ThreadLocal在调用set或者get方法的时候都会去检测key有没有被回收(调用expungeStaleEntry方法),然后将其value值设置为null,这个方法和WeakHashMap对象的expungeStaleEntries()方法一样。因此value在key被gc后可能还会存活一段时间,但最终也会被回收,但是若不再调用get或者set方法时,那么这个value就在线程存活期间无法被释放。 expungeStaleEntry方法代码如下:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
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 {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
本文是为了探究Looper原理记录ThreadlLocal笔记,参考了很多文献如下:
https://blog.csdn.net/u013256816/article/details/51776846
https://my.oschina.net/xianggao/blog/392440?fromerr=CLZtT4xC
《Android开发艺术探索》
网友评论