ThreadLocal应用
ThreadLocal作用
ThreadLocal可以保证当前拿到的变量是属于当前访问的线程。也就是每个线程自己的独立小空间。实现了线程之间的数据隔离。
ThreadLocal API
ThreadLocal隔离性
public class ThreadTime {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
threadLocal.set("哈哈");
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
}
}).start();
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
}
}
结果:
ThreadLocal统计方法花费的时间
public class ThreadTime {
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static final void beginThread() {
threadLocal.set(System.currentTimeMillis());
}
public static final long endThread() {
return System.currentTimeMillis() - threadLocal.get();
}
public static void main(String[] args) throws InterruptedException {
ThreadTime.beginThread();
TimeUnit.SECONDS.sleep(1);
System.out.println("cost: " + ThreadTime.endThread() + " mills");
}
}
结果:
InheritableThreadLocal实现父子线程数据传递
public static void main(String[] args) {
InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
for (int i = 0 ; i < 5 ; i++) {
//每个线程的序列号,希望在子线程中能够拿到
threadLocal.set(i);
//这里来了一个子线程,我们希望可以访问上面的threadLocal
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
ThreadLocal源码
- set方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程对应的本地Map映射
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null)
//将值设置到本地map中
map.set(this, value);
else//如果map为空
//创建线程本地map,将value值进行初始化
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
//初始化线程本地Map
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
解释:
获取当前线程对应的map,如果map不为空,就将值设置到map中。如果map为空就将创建Map结构,然后将值放入到链表中。
注意:
可以发现ThreadLocalMap的构造器采用HashMap的套路,初始化节点数组,然后使用键key进行按位与运算计算数组下标,设置最大临界值为初始化容量。
- get方法
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的本地map映射
ThreadLocalMap map = getMap(t);
// 如果map不为空
if (map != null) {
// 获取映射map的链表
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果节点不为空,返回节点的值
if (e != null)
return (T)e.value;
}
//返回初始化的值
return setInitialValue();
}
private T setInitialValue() {
// 获取初始化时传入的值
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的本地map映射
ThreadLocalMap map = getMap(t);
// 如果映射map不为空,设置当前值
if (map != null)
map.set(this, value);
else // 如果映射map为空,那么创建map映射,然后初始化值到链表中
createMap(t, value);
return value;
}
解释:
① 首先获取当前线程的本地map映射。
② 如果map不为空,那么就走Hashmap的套路,通过键按位与运算计算下标,然后通过下标获取值。
③ 否则会再判断下map是否为空,如果空就创建下ThreadLocalMap,但是最后的效果就是拿到初始化传入的值,初始化传入的值就是null,除非自己重写initialValue方法。
ThreadLocal如何保证线程安全
看源码set方法:
ThreadLocal内存泄漏
- 以getEntry()为例:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
//如果找到key,直接返回
return e;
else
//如果找不到,就会尝试清理,如果你总是访问存在的key,那么这个清理永远不会进来
return getEntryAfterMiss(key, i, e);
}
- getEntryAfterMiss()实现:
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
// 整个e是entry ,也就是一个弱引用
ThreadLocal<?> k = e.get();
//如果找到了,就返回
if (k == key)
return e;
if (k == null)
//如果key为null,说明弱引用已经被回收了
//那么就要在这里回收里面的value了
expungeStaleEntry(i);
else
//如果key不是要找的那个,那说明有hash冲突,这里是处理冲突,找下一个entry
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
真正用来回收value的是expungeStaleEntry()方法,在remove()和set()方法中,都会直接或者间接调用到这个方法进行value的清理:
ThreadLocal为了避免内存泄露,也算是花了一番大心思。不仅使用了弱引用维护key,还会在每个操作上检查key是否被回收,进而再回收value。但是从中也可以看到,ThreadLocal并不能100%保证不发生内存泄漏。
建议:当你不需要这个ThreadLocal变量时,主动调用remove()。