ThreadLocal顾名思义就是线程本地变量
1,ThreadLocal介绍
线程局部变量,就是每个线程都有一个自己独一的变量副本,举个例子,大家就能明白
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("main");
Thread a = new Thread() {
@Override
public void run() {
super.run();
tl.set("A");
System.out.println("a = [" + tl.get() + "]");
}
};
Thread b = new Thread() {
@Override
public void run() {
super.run();
tl.set("B");
System.out.println("b = [" + tl.get() + "]");
}
};
a.start();
b.start();
System.out.println("main = [" + tl.get() + "]");
上面代码的输出结果是
a = [A]
main = [main]
b = [B]
如上可以看出,虽然不同的线程访问了同一个对象tl,但是它们通过tl获取到的对象却是不同的,也就是说ThreadLocal为每个线程保存了一个副本
2,ThreadLocal的原理
1,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);
}
从上面的源码可以看出,ThreadLocal.set方法的本质是先通过当前线程获取到当前线程的ThreadLocalMap,然后调用ThreadLocalMap.set(key, value)方法,如果为空的情况下,使用当前线程来创建ThreadLocalMap,如下
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
getMap源码如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如上可以看出,ThreadLocalMap是线程的属性之一,因此它是和当前线程相关的
2,get原理
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中获取,如果没有获取的,则返回默认null
3,ThreadLocal.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;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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);
}
}
从上面的源码可以看出,ThreadLocalMap内部仅仅维护了Entry[] table,数组。其中Entry实体中对应的key为弱引用(下文会将为什么会用弱引用),在第一次放入数据时,会初始化数组长度(为16),定义数组扩容阀值(当前默认数组长度的2/3)。
如果key使用强引用,那么当引用ThreadLocal的对象被回收了,但ThreadLocalMap中还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致内存泄漏。
弱引用的key也可能带来一些问题,就是ThreadLocalMap可能有很多null为key的数据,而此时的value依然是被强引用,因此
注意:不管是调用ThreadLocal的set()还是get()方法,都会去清除key==null的数据。
- ThreadLocal本质是操作线程中ThreadLocalMap来实现本地线程变量的存储的
- ThreadLocalMap是采用数组的方式来存储数据,其中key(弱引用)指向当前ThreadLocal对象,value为设的值
- ThreadLocal为内存泄漏采取了处理措施,在调用ThreadLocal的get(),set(),remove()方法的 时候都会清除线程ThreadLocalMap里所有key为null的Entry
- 在使用ThreadLocal的时候,我们仍然需要注意,避免使用static的ThreadLocal,分配使用了ThreadLocal后,一定要根据当前线程的生命周期来判断是否需要手动的去清理ThreadLocalMap中清key==null的Entry。