ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。 ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
在Spring,Hibernate,Struts2中对于ThreadLocal都有广泛的应用,在Hibernate中获取connection也是通过ThreadLocal来封装connection对象,实现了连接connection之间不会相互影响的效果。在Struts2中通过ThreadLocal来封装每个请求,让每个用户的request请求分离达到不同用户线程之间获取不同的request对象效果。
ThreadLocal原理
ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。使用一个Map来把线程作为key,对象作为value来存储对象,这样能上达到各个线程能够存储一个对象,让每个线程的对象独立分割开来的效果。
对于ThreadLocal的基本原理,我们可以提供一个基本模拟版本给大家参考。
publicclassSimpleThreadLocal {
privateMap valueMap = Collections.synchronizedMap(newHashMap());
publicvoidset(Object newValue) {
//键为线程对象,值为本线程的变量副本
valueMap.put(Thread.currentThread(), newValue);
}
publicObject get() {
Thread currentThread = Thread.currentThread();
//返回本线程对应的变量
Object o = valueMap.get(currentThread);
//如果在Map中不存在,放到Map中保存起来
if(o == null&& !valueMap.containsKey(currentThread)) {
o = initialValue();
valueMap.put(currentThread, o);
}
returno;
}
publicvoidremove() {
valueMap.remove(Thread.currentThread());
}
publicObject initialValue() {
returnnull;
}
}
SimpleThreadLocal 一共提供了4个方法
void set(Object newValue):根据当前线程,来设置当前线程的对象值,
Object get():获取当前线程的对象
void remove():删除当前线程里的对象
initialValue():设置当前对象的默认初始值
ThreadLocal源码
以下是JDK1.5中的TheadLocal部分源码:
可以看到,源码中的方法与我们上述模拟的ThreadLocal简单版本一致。而在方法内部做了一些更加复杂的控制。
1.首先从类的成员变量有3个int类型的变量。其中threadLocalHashCode是类的实例变量,nextHashCode表示了下一个ThreadLocal实例的threadLocalHashCode的值,而HASH_INCREMENT是一个常量,表示每次threadLocalHashCode的增量。
threadLocalHashCode调用了nextHashCode()这个方法。这个方法的目的是将下一个hashCode的值赋值给了threadLocalHashCode,然后nextHashCode进行自增。
类的实例变量会在类每次实例化的时候初始化,所以每次实例化ThreadLocal的时候,它的threadLocalHashCode都会自增。
ThreadLocal是作为一个工具拿来给不同的对象来使用,为了区分不同的ThreadLocal的实例,所以需要定义threadLocalHashCode来区别不同的ThreadLocal实例。
2.在上面代码中,我们可以看到有一个ThreadLocalMap的类,这个类是ThreadLocal的内部类,受篇幅限制,并没有截图出来。这个内部类的实例却在Thread类中定义。
从上可以看到,每一个线程的实例的都有自己的成员变量threadLocals,也就是说是threadLocals这个对象依赖于每个线程存在。当调用set方法时,如果当前线程的threadLocals不为空,就把当前ThreadLocal实例做为key,要保持的对象做为值,设置到当前线程的ThreadLocalMap中;如果没有threadLocals,就创建并且设置。调用get方法的时候,从当前ThreadLocal对象和当前线程,取得对应的缓存对象。
ThreadLocal类通过操作每一个线程特有的ThreadLocalMap对象,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。在JDK1.5之后,放在ThreadLocal中的对象,都是泛型,这样可以避免使用Object类型的强制类型转化。
ThreadLocal在Hibernate中的使用
以下为Hibernate中使用ThreadLocal的范例,我们可以学习一下
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
//定义SessionFactory
private static final SessionFactory sessionFactory;
static {
try {
// 通过默认配置文件Hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
//获取当前线程中的Session
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session还没有打开,则新开一个Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的Session保存到线程局部变量中
}
return s;
}
public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为Session类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
示例是在开启数据库连接的session对象的过程。其实,所有类似于这种缓存对象的代码都一样。首先从ThreadLocal中取当前线程的对象,如果对象为null,就重新创建对象,并把创建好的对象放到ThreadLocal里面。ThreadLocal持有的对象能够保持各个线程之间对象的独立。
ThreadLocal使用步骤
1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。
本文作者:易泉梁(点融黑帮),耿直程序猿一枚,会写点代码,喜欢享受生活,上不了厅堂,下得了厨房,喜欢运动,排球(二传),羽毛球,乒乓球水平可以虐菜鸟.人其实是可以快乐地生活的,只是我们自己选择了复杂,选择了叹息!