`
wsmajunfeng
  • 浏览: 491810 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

正确理解ThreadLocal

 
阅读更多

 

首先, ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过 ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。 各个线程中访问的是不同的对象。

另外,说 ThreadLocal 使得各线程能够保持各自独立的一个对象,并不是通过 ThreadLocal.set() 来实现的,而是通过每个线程中进行 new 对象操作来创建对象,每个线程创建一个,不是什么对象的拷贝或副本。 通过 ThreadLocal.set() 将这个新创建的对象的引用保存到各线程自己的一个 mapThreadLocalMap )中,每个线程都有这样一个 map ,执行 ThreadLocal.get() 时,各线程从自己的此 mapThreadLocalMap )中取出放进去的对象,因此取出来的是各线程自己的对象, ThreadLocal 实例是作为 mapkey 来使用的。

如果 ThreadLocal.set() 进去的东西本来就是多个线程共享的同一个对象,那么多个线程的 ThreadLocal.get() 取得的还是这个共享对象本身,还是有并发访问问题。

下面来看一个 hibernate 中典型的 ThreadLocal 的应用:

 

 写道
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;
}

 

可以看到在 getSession() 方法中,首先判断当前线程中有没有放进去 Session 对象,如果还没有,那么通过 sessionFactory().openSession() 来创建一个 Session 对象( s ),再将此 session set 到线程中,实际是作为 value 放到当前线程的 ThreadLocalMap 这个 map 中,这时,对于这个 session 的唯一引用就是当前线程中的那个 ThreadLocalMap (下面会讲到),而 threadSession 是这个 map 中的 key ,要取得这个 session 可以通过 threadSession.get() 来得到,里面执行的操作实际是先取得当前线程中的 ThreadLocalMap ,然后将 threadSession 作为 key 将对应的值( session )取出。这个 session 相当于线程的私有变量,而不是 public 的。

显然,其他线程中是取不到这个 session 的,他们也只能取到自己的 ThreadLocalMap 中的东西。要是 session 是多个线程共享使用的,那还不乱套了。

试想如果不用 ThreadLocal 怎么来实现呢?可能就要在 action 中创建 session ,然后把 session 一个个传到 servicedao 中,这可够麻烦的。或者可以自己定义一个静态的 map ,将当前 threadThread.currentThread() )作为 key ,创建的 session 作为 valueputmap 中,应该也行,这也是一般人的想法,但事实上, ThreadLocal 的实现刚好相反,它是在每个线程中有一个 mapThreadLocalMap ),而将 ThreadLocal 实例作为 key ,这样每个 map 中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。

总之, ThreadLocal 不是用来解决多线程访问共享对象问题的,而主要是提供了一种保存对象的方法和一种方便的避免参数传递麻烦的对象访问方式。归纳了两点:
1
、每个线程中都有一个自己的 ThreadLocalMap 类对象,可以将线程自己的对象(作为 value )保持到其中,各管各的,线程可以正确的访问到自己的对象。
2
、将一个共用的 ThreadLocal 静态实例作为 key ,将不同对象的引用保存到不同线程的 ThreadLocalMap 中,然后在线程执行的各处通过这个静态 ThreadLocal 实例的 get() 方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

当然如果要把本来线程共享的对象通过 ThreadLocal.set() 放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意 get() 到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没 必要放到线程中。

ThreadLocal 的应用场合,我觉得最适合的是按每个线程对应一个实例的方式访问对象,并且这个对象很多地方都要用到。比如:

一个验证流程包括很多验证环节,每个验证环节用一个类( validator )表示,每个验证环节都要用到同一个对象(比如会员类型: memberType ),也就是说这个 memberType 要在整个验证流程中的各验证环节进行传递。一般的做法是将此对象作为方法的一个参数一个个传到各验证环节中去。

这种场景下就很适合采用 ThreadLocal 来实现。一个线程(这个场景下即一个验证流程)要用到一个对象实例( memberType ),而这个对象要在很多地方(每个验证环节)都用到。

看ThreadLocal类的源码可知作为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,而且是final的,用来区分不同的ThreadLocal实例,ThreadLocal类主要是作为工具类来使用。nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics