一、概述
ThreadLocal
提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的,而使用ThreadLocal
创建的变量只能被当前线程访问,其他线程则无法访问和修改。
二、使用示例
简单的使用:
private void testThreadLocal() {
Thread t = new Thread() {
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
@Override
public void run() {
super.run();
mStringThreadLocal.set("droidyue.com");
mStringThreadLocal.get();
}
};
t.start();
}
在mybatis
里面的使用,ThreadLocal
能够实现当前线程的操作都是用同一个Connection
,保证了事务:
public class MybatisUtil {
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
private static SqlSessionFactory sqlSessionFactory;
static{
try {
Reader reader = Resources.getResourceAsReader("mybatis.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//获取SqlSession
public static SqlSession getSqlSession(){
//从当前线程中获取SqlSession对象
SqlSession sqlSession = threadLocal.get();
//如果SqlSession对象为空
if(sqlSession == null){
//在SqlSessionFactory非空的情况下,获取SqlSession对象
sqlSession = sqlSessionFactory.openSession();
//将SqlSession对象与当前线程绑定在一起
threadLocal.set(sqlSession);
}
//返回SqlSession对象
return sqlSession;
}
// 关闭SqlSession与当前线程分开
public static void closeSqlSession(){
//从当前线程中获取SqlSession对象
SqlSession sqlSession = threadLocal.get();
//如果SqlSession对象非空
if(sqlSession != null){
//关闭SqlSession对象
sqlSession.close();
//分开当前线程与SqlSession对象的关系,目的是让GC尽早回收,避免内存泄漏
threadLocal.remove();
}
}
}
三、实现原理
下面是ThreadLocal
的set
方法的实现流程:
public void set(T value) {
//首先获取当前线程
Thread t = Thread.currentThread();
//利用当前线程作为句柄获取一个ThreadLocalMap的对象
ThreadLocalMap map = getMap(t);//获取的实际上是Thread对象的threadLocals变量
//如果上述ThreadLocalMap对象不为空,则设置值,否则创建这个ThreadLocalMap对象并设置值
if (map != null)
map.set(this, value);
else
createMap(t, value); //新建ThreadLocalMap对象,并设置初始值。
}
总结:实际上ThreadLocal
的值是放入了当前线程的一个ThreadLocalMap
实例中,而ThreadLocalMap
的key
是LocalThread
对象本身,value
则是要存储的对象,所以只能在本线程中访问,其他线程无法访问。
Notes: ThreadLocal
本身并不存储值,它只是作为一个key
来让线程从ThreadLocalMap
获取value
。
四、避免内存泄露
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
,都调用它的remove()
方法,清除数据。
具体可以参考深入分析 ThreadLocal 内存泄漏问题