文章目录
一、ThreadLocal是什么
ThreadLocal是JDK提供的,线程本地变量。也就是如果创建了一个ThreadLocal变量,那么访问这个变量的所有线程都会有这个变量的本地拷贝,多个线程操作这个变量的时候,实际上是操作本地变量的拷贝。实现了多个线程中变量的隔离,避免了线程安全的问题。
一个ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的(每一个线程只能看到自己线程的值)
二、ThreadLocal原理
ThreadLocal存储的值,并不是在ThreadLocal实例中,而是存储在每一个Thread实例的ThreadLocalMap中。ThreadLocalMap为Thread类的一个变量。
public class Thread implements Runnable {
//......
ThreadLocalMap threadLocals;
ThreadLocalMap inheritableThreadLocals;
//......
}
我们通过ThreadLocal的set方法进行分析。
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前使用ThreadLoacal实例的线程
ThreadLocal.ThreadLocalMap map = this.getMap(t); //获取当前线程的ThreaLocalMap对象
if (map != null) {
map.set(this, value);//将值value存储在当前线程的map中
} else {
this.createMap(t, value);//创建线程,并将值value存储到当前线程的ThreaLocalMap中
}
}
//获取当前线程的ThreadLoacalMap对象
ThreadLocal.ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建当前线程的ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
我们可以简单的把ThreadLocalMap理解成Map对象,key为当前的ThreadLocal对象,value则可以是任何的。当我们使用ThreadLocal对象set一个新的值的时候。首先会获取该线程的ThreadLoacalMap对象,如果能够获取到则设置该变量,如果获取不到则创建,并设置。而这些变量,都是属于具体的某一个Thread实例的。不同的线程之间这些变量都是隔离的。
三、ThreadLocal怎么用
根据上面的总结,使用场景可以概括为:每个线程需要单独的变量实例,且这些实例不能被其他线程所共享。
- 线程安全
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String formatDate(Date date) {
return format1.get().format(date);
}
}
因为,SimpleDateFormat是线程非安全的,在多线程的环境中,直接调用同一个DateFormat实例的时候,就会出现线程不安全现象。使用ThreadLocal则可以规避此问题。
-
存储用户Session
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
-
存储数据库连接
-
进行事务操作,用于存储线程事务信息。
四、ThreadLocal内存泄露问题
上图详细的展示了ThreadLocal变量的引用关系。阐述了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
- Thread中有个map,就是ThreadLocalMap
- ThreadLocalMap的key是ThreadLocal,值为任意类型
- ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
- 突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
原因:如果ThreadLocal没有外部强引用,那么在发生垃圾回收的时候,ThreadLocal就必定会被回收,而ThreadLocal又作为Map中的key,ThreadLocal被回收就会导致一个key为null的entry,外部就无法通过key来访问这个entry,垃圾回收也无法回收,这就造成了内存泄漏
解决办法是每次使用完ThreadLocal都调用它的remove()方法清除数据,或者按照JDK建议将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。