ThreadLocal ThreadLocalMap浅谈
ThreadLocal是什么?
先上一段源码中Threadlocal类的定义
This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).
大概意思是说,这个类提供了一个本地线程的变量。这些变量自己独立初始化,区别于线程里其他的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。
总结一下:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
public class ThreadTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
protected Integer initialValue(){
return 0;
}
};
// 值传递
public static void main(String[] args) {
testValue();
}
public static void testValue(){
for (int i = 0; i < 5; i++){
new Thread(() -> {
Integer temp = threadLocal.get();
System.out.println(temp);
threadLocal.set(temp + 5);
System.out.println("current thread is " + Thread.currentThread().getName() + " num is " + threadLocal.get());
}, "thread-" + i).start();
}
}
}
看一段代码,这段代码的输出结果如下,这也就说明了ThreadLocal是线程内的变量,线程之间的ThreadLocal是隔离的。
current thread is thread-1 num is 5
current thread is thread-3 num is 5
current thread is thread-0 num is 5
current thread is thread-4 num is 5
current thread is thread-2 num is 5
如何实现?
ThreadLocal<String> ThreadLocal = new ThreadLocal();
ThreadLocal .set("哈哈哈");
String name = localName.get();
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
来看一下对threadlocal的set操作,过程是
1.获取当前线程
2.获取当前线程的threadLocalMap
3.再对map进行set操作,其中key是this,即threadLocal对象,value是要set的value
get操作类似
1.获取当前线程
2.获取当前线程的threadLocalMap
3.获取当前线程threadLocalMap.Entry,然后获取value
综上,对threadLocal的操作其实是对threadLocalMap的操作
threadLocalMap
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocalMap是Thread的静态内部类。这里需要注意的是,ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。
在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
每一个线程都有一个自己的threadLocalMap,其结构是一个长度为16的Entry数组,数组里的元素储存对应的ThreadLocal+value。为什么是数组,是因为一个线程里可能会同时有多个ThreadLocal对象。
//在某一线程声明了ABC三种类型的ThreadLocal
ThreadLocal<A> sThreadLocalA = new ThreadLocal<A>();
ThreadLocal<B> sThreadLocalB = new ThreadLocal<B>();
ThreadLocal<C> sThreadLocalC = new ThreadLocal<C>();
hash问题
已知是往Entry数组存入数据,具体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.
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();
}
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
key.threadLocalHashCode 会调用nextHashCode()。在ThreadLocal类中,还包含了一个static修饰的AtomicInteger提供原子操作的Integer类)成员变量(即类变量)和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。
也就是说每个ThreadLocal对象都有一个hash threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647,这样保证不同的threadLocal变量会存在Entry不同的位置上。
内存泄漏
上图是ThreadLocal等变量之间的引用关系图,实线表示强引用,虚线表示弱引用。其中Entry的key是threadLocal的弱引用。
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露。
使用ThreadLocal的set方法之后,通过显示的调用remove方法,可以避免发生内存泄漏。
参考引用
https://juejin.im/post/5a64a581f265da3e3b7aa02d
https://www.cnblogs.com/xzwblog/p/7227509.html
https://www.jianshu.com/p/3c5d7f09dfbd
https://juejin.im/entry/5662895900b0bf3758a69736
https://www.cnblogs.com/yinbiao/p/10728909.html