`
dsxwjhf
  • 浏览: 70100 次
  • 性别: Icon_minigender_1
  • 来自: 安徽
社区版块
存档分类
最新评论

【转】正确理解 ThreadLocal

阅读更多
关于 ThreadLocal ,源码读起来有些费劲 ------ 如果你对它的原理事先没有一个大概的了解的话。网上有很多文章模棱两可,甚至有不少错误。有幸看到这位作者的博客,搬过来备忘一下。

原文地址: 正确理解 ThreadLocal

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

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

如果 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 ,如果还没有,那么通过 getSessionFactory().openSession() 来创建一个 session ,再将 session set 到线程中,实际是放到当前线程的 ThreadLocalMap 这个 map 中。这时,对于这个 session 的唯一引用就是当前线程中的那个 ThreadLocalMap (下面会讲到),而 threadSession 作为这个值的 key ,要取得这个 session 可以通过 threadSession.get() 来得到,里面执行的操作实际是先取得当前线程中的 ThreadLocalMap ,然后将 threadSession 作为 key 将对应的值取出。这个 session 相当于线程的私有变量,而不是 public 的。

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

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



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


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

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



下面来看看 ThreadLocal 的实现原理(jdk1.5源码)
    public class ThreadLocal<T> {  
        /** 
         * ThreadLocals rely on per-thread hash maps attached to each thread 
         * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal 
         * objects act as keys, searched via threadLocalHashCode.  This is a 
         * custom hash code (useful only within ThreadLocalMaps) that eliminates 
         * collisions in the common case where consecutively constructed 
         * ThreadLocals are used by the same threads, while remaining well-behaved 
         * in less common cases. 
         */  
        private final int threadLocalHashCode = nextHashCode();  
      
        /** 
         * The next hash code to be given out. Accessed only by like-named method. 
         */  
        private static int nextHashCode = 0;  
      
        /** 
         * The difference between successively generated hash codes - turns 
         * implicit sequential thread-local IDs into near-optimally spread 
         * multiplicative hash values for power-of-two-sized tables. 
         */  
        private static final int HASH_INCREMENT = 0x61c88647;  
      
        /** 
         * Compute the next hash code. The static synchronization used here 
         * should not be a performance bottleneck. When ThreadLocals are 
         * generated in different threads at a fast enough rate to regularly 
         * contend on this lock, memory contention is by far a more serious 
         * problem than lock contention. 
         */  
        private static synchronized int nextHashCode() {  
            int h = nextHashCode;  
            nextHashCode = h + HASH_INCREMENT;  
            return h;  
        }  
      
        /** 
         * Creates a thread local variable. 
         */  
        public ThreadLocal() {  
        }  
      
        /** 
         * Returns the value in the current thread's copy of this thread-local 
         * variable.  Creates and initializes the copy if this is the first time 
         * the thread has called this method. 
         * 
         * @return the current thread's value of this thread-local 
         */  
        public T get() {  
            Thread t = Thread.currentThread();  
            ThreadLocalMap map = getMap(t);  
            if (map != null)  
                return (T)map.get(this);  
      
            // Maps are constructed lazily.  if the map for this thread  
            // doesn't exist, create it, with this ThreadLocal and its  
            // initial value as its only entry.  
            T value = initialValue();  
            createMap(t, value);  
            return value;  
        }  
      
        /** 
         * Sets the current thread's copy of this thread-local variable 
         * to the specified value.  Many applications will have no need for 
         * this functionality, relying solely on the {@link #initialValue} 
         * method to set the values of thread-locals. 
         * 
         * @param value the value to be stored in the current threads' copy of 
         *        this thread-local. 
         */  
        public void set(T value) {  
            Thread t = Thread.currentThread();  
            ThreadLocalMap map = getMap(t);  
            if (map != null)  
                map.set(this, value);  
            else  
                createMap(t, value);  
        }  
      
        /** 
         * Get the map associated with a ThreadLocal. Overridden in 
         * InheritableThreadLocal. 
         * 
         * @param  t the current thread 
         * @return the map 
         */  
        ThreadLocalMap getMap(Thread t) {  
            return t.threadLocals;  
        }  
      
        /** 
         * Create the map associated with a ThreadLocal. Overridden in 
         * InheritableThreadLocal. 
         * 
         * @param t the current thread 
         * @param firstValue value for the initial entry of the map 
         * @param map the map to store. 
         */  
        void createMap(Thread t, T firstValue) {  
            t.threadLocals = new ThreadLocalMap(this, firstValue);  
        }  
      
        .......  
      
        /** 
         * ThreadLocalMap is a customized hash map suitable only for 
         * maintaining thread local values. No operations are exported 
         * outside of the ThreadLocal class. The class is package private to 
         * allow declaration of fields in class Thread.  To help deal with 
         * very large and long-lived usages, the hash table entries use 
         * WeakReferences for keys. However, since reference queues are not 
         * used, stale entries are guaranteed to be removed only when 
         * the table starts running out of space. 
         */  
        static class ThreadLocalMap {  
      
        ........  
      
        }  
      
    }  

可以看到ThreadLocal类中的变量只有这3个int型:
private final int threadLocalHashCode = nextHashCode();  
private static int nextHashCode = 0;  
private static final int HASH_INCREMENT = 0x61c88647; 

而作为 ThreadLocal 实例的变量只有 threadLocalHashCode 这一个, nextHashCode 和 HASH_INCREMENT 是 ThreadLocal 类的静态变量,实际上 HASH_INCREMENT 是一个常量,表示了连续分配的两个 ThreadLocal 实例的 threadLocalHashCode 值的增量,而 nextHashCode 则表示了即将分配的下一个 ThreadLocal 实例的 threadLocalHashCode 的值。

可以来看一下创建一个 ThreadLocal 实例即 new ThreadLocal() 时做了哪些操作,从上面看到构造函数ThreadLocal() 里什么操作都没有,唯一的操作是这句:
private final int threadLocalHashCode = nextHashCode();  

那么 nextHashCode() 做了什么呢:
private static synchronized int nextHashCode() {  
    int h = nextHashCode;  
    nextHashCode = h + HASH_INCREMENT;  
    return h;  
}  

就是将 ThreadLocal 类的下一个 hashCode 值即 nextHashCode 的值赋给实例的threadLocalHashCode ,然后 nextHashCode 的值增加 HASH_INCREMENT 这个值。

因此 ThreadLocal 实例的变量只有这个 threadLocalHashCode ,而且是 final 的,用来区分不同的 ThreadLocal 实例。 ThreadLocal 类主要是作为工具类来使用,那么 ThreadLocal.set() 进去的对象是放在哪儿的呢?
看一下上面的 set() 方法,两句合并一下成为:
ThreadLocalMap map = Thread.currentThread().threadLocals;  

这个 ThreadLocalMap 类是 ThreadLocal 中定义的内部类,但是它的实例却用在 Thread 类中:
    public class Thread implements Runnable {  
        ......  
      
        /* ThreadLocal values pertaining to this thread. This map is maintained 
         * by the ThreadLocal class. */  
        ThreadLocal.ThreadLocalMap threadLocals = null;    
        ......  
    }  

再看这句:
    if (map != null)  
        map.set(this, value);  

也就是将该 ThreadLocal 实例作为 key ,要保持的对象作为值,设置到当前线程的 ThreadLocalMap 中。 get()方法同样大家看了代码也就明白了, ThreadLocalMap 类的代码太多了,我就不帖了,自己去看源码吧。



最后转载这篇文章的“作者”我再罗嗦一句: ThreadLocal 的关键是:以线程共享的 ThreadLocal 实例本身作为 key ,把 set 进去的值放在当前线程的 ThreadLocalMap 中,从而实现线程隔离。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics