ThreadLocal的作用及使用场景
作用
ThreadLocal为每一个线程保存独立的副本信息,这样每个线程都可以修改和使用自己的副本对象,做到了线程隔离,不会影响其他线程的副本使用,确保了线程安全。
场景
最典型的就是使用SimpleDateFormat类的时候,会把SimpleDateFormat储存到ThreadLocal中做线程隔离,因为SimpleDateFormat是线程不安全的,避免了频繁地去创建对象。
关于SimpleDateFormat类线程不安全的问题可以看我的另外一篇文章:
为什么SimpleDateFormat要放到ThreadLocal里?_王忠的博客-CSDN博客
Thread、ThreadLocal、ThreadLocalMap关系
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
一个线程里,维护了一个成员变量ThreadLocalMap。
public class ThreadLocal<T> {
...
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;
}
...
private Entry[] table;
}
}
ThreadLocalMap为ThreadLocal的一个静态内部类,他的key为你创建的ThreadLocal对象,且是弱引用。他的value即为你想要保存的副本对象。ThreadLocalMap里可以保存多对ThreadLocal和副本对象。
ThreadLocal内存泄漏
这张图老生常谈了,讲内存泄漏问题时的必需图。
上面讲了Thread、ThreadLocal、ThreadLocalMap之间的关系,再根据这张图来看,当我们把创建的ThreadLocal对象置为null的时候,ThreadLocal这个key在下次gc的时候就会被回收;此时,对应的value因为存在一条引用链路,符合可达性分析,不会被回收掉,但同时已经无法访问到该value了,所以造成了内存泄漏。
如果使用完之后线程就结束销毁了那倒没啥事,但是很多场景我们是放在线程池里去用的,线程会保持存在的状态,那就会有内存泄漏的问题。
解决内存泄漏的方法
源码考虑到这种脏entry(key为null)的问题,所以在set、getEntry、remove等方法里都对脏entry做了清除,有兴趣的可以自己去看下源码。
工作中最常见的避免内存泄漏的方式就是在finally代码块里进行remove操作。
ThreadLocal类的使用
initialValue
protected T initialValue() {
return null;
}
如果不想用set方法,调用get方法时直接能进行初始化,就要重写initialValue方法,在方法里进行副本对象的初始化。
该方法返回当前线程局部变量的初始化值。当第一次使用get方法访问变量时调用该方法,但若该线程在这之前调用了set方法,则不会调用initialValue方法。正常情况下,该方法最多被调用一次,但是如果后续调 用了remove,然后再调用get,则该方法可能再次被调用。
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
withInitial
java1.8之后的版本,idea会更推荐使用withInitial方法来进行初始化操作,而非initialValue。
private static final ThreadLocal<Map<String, Object>> mapLocal =
ThreadLocal.withInitial(HashMap::new);