ThreadLocal解析
ThreadLocal是什么
ThreadLocal是Java中的一个泛型类,用来存储只有本线程能访问的对象。
如何使用
实例化ThreadLocal的对象后,使用该对象的get()和set()方法,存取对象。每个线程只能访问自己存储的对象。
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("hello world");
System.out.println(threadLocal.get());
Thread newThread = new Thread(()-> {
threadLocal.set("this is a new thread.");
System.out.println(threadLocal.get());
});
newThread.run();
}
}
/*
* 输出结果为:
* hello world
* this is a new thread.
*/
如何打破线程间的隔离
InheritableThreadLocal类是ThreadLocal类的子类,它允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。
public class ThreadLocalExample {
public static void main(String[] args) {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("hello world, it's father thread.");
System.out.println(inheritableThreadLocal.get());
Thread sonThread = new Thread(()-> {
System.out.println(inheritableThreadLocal.get());
});
sonThread.run();
}
}
/*
* 输出结果为:
* hello world, it's father thread.
* hello world, it's father thread.
*/
源码分析
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
在Thread的源码中,thread对象拥有一个threadLocals的变量。根据注解,可以理解为此变量由ThreadLocal类维护。
public void set(T value) {
Thread t = Thread.currentThread();
// getMap(t)即返回t.threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 将本线程(this)作为key传入map,委托给map.set()实现
map.set(this, value);
else
// 如果Thread实例中没有找到ThreadLocalMap对象,就创建该对象
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 委托给map.getEntry()实现
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
在ThreadLocal中的set()和get()方法,都要利用getMap(thread)方法,获得ThreadLocalMap对象。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如果Thread中的ThreadLocalMap为空,则ThreadLocal为它创建一个新的TreadLocalMap对象。
// Entry继承自WeakReference,是弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
// key是弱引用
super(k);
// value是强引用
value = v;
}
}
ThreadLocalMap对象是ThreadLocal类的静态内部类,使用Thread的hash值为key,维护的Entry数组,初始大小为16。
Entry与HashMap中的实现的Map.Entry有所不同,它不包含指向下一个节点的引用,即next值。
它继承了WeakReference父类,再来看看弱引用的特性。弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收。
如果一个ThreadLocal实例没有指向它的强引用,它会被垃圾回收,如果value没有置为空,可能会产生内存泄露,需要在线程结束前手动置空。
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
扩容的触发阈值为2/3。
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
hash值的计算方式,固定增加0x61c88647。
int i = key.threadLocalHashCode & (table.length - 1);
针对查找某个key,查找value,它的hash值计算过程如上代码所示,hash值与entry数组的长度取与。因为entry数组容量为2的n次方,每次扩容都是之前的两倍,因此table.length - 1 ,即取hash值的后n位。
之前从TreadLocal的get(),set()方法看到,它的实现主要委托给TreadLocalMap对象的getEntry(),set()实现。在这里看看他们的源码实现。
private Entry getEntry(ThreadLocal<?> key) {
// 计算key的哈希值,获得位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 如果当前key与要查询的key不相等,委托给getEntryAfterMiss()方法处理
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
// 查找到相同的key
if (k == key)
return e;
// 当前位置key为空
if (k == null)
expungeStaleEntry(i);
// 查找下一位置
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
可以看出在TreadLocalMap中解决hash冲突的方法是线性查找法。
再来看看set()方法。
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.
// 因为set方法用于创建Entry和替换原有的值用的一样多
// 与getEntry不同,不用短路失败
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();
// value替换
if (k == key) {
e.value = value;
return;
}
// key已失效,但value存在,抹去陈旧value
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}