正確理解ThreadLocal

首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,通常狀況下,經過ThreadLocal.set() 到線程中的對象是該線程本身使用的對象,其餘線程是不須要訪問的,也訪問不到的。 各個線程中訪問的是不一樣的對象。 

另外,說ThreadLocal使得各線程可以保持各自獨立的一個對象,並非經過ThreadLocal.set()來實現的,而是經過每一個線程中的new 對象 的操做來建立的對象,每一個線程建立一個,不是什麼對象的拷貝或副本。 經過ThreadLocal.set()將這個新建立的對象的引用保存到各線程的本身的一個map中,每一個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從本身的map中取出放進去的對象,所以取出來的是各自本身線程中的對象,ThreadLocal實例是做爲map的key來使用的。 

若是ThreadLocal.set()進去的東西原本就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題。 

下面來看一個hibernate中典型的ThreadLocal的應用:  java

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,再將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;  
    ......
}

再看這句:  session

if (map != null)
            map.set(this, value);



也就是將該ThreadLocal實例做爲key,要保持的對象做爲值,設置到當前線程的ThreadLocalMap 中,get()方法一樣你們看了代碼也就明白了,ThreadLocalMap 類的代碼太多了,我就不帖了,本身去看源碼吧。 


貼個例子吧: 多線程


public class Student {
    private int age = 0;   //年齡
 
    public int getAge() {
        return this.age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
 

public class ThreadLocalDemo implements Runnable {
    //建立線程局部變量studentLocal,在後面你會發現用來保存Student對象
    private final static ThreadLocal studentLocal = new ThreadLocal();
 
    public static void main(String[] agrs) {
        ThreadLocalDemo td = new ThreadLocalDemo();
        Thread t1 = new Thread(td, "a");
        Thread t2 = new Thread(td, "b");
        t1.start();
        t2.start();
    }
 
    public void run() {
        accessStudent();
    }
 
    /**
     * 示例業務方法,用來測試
     */
    public void accessStudent() {
        //獲取當前線程的名字
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");
        //產生一個隨機數並打印
        Random random = new Random();
        int age = random.nextInt(100);
        System.out.println("thread " + currentThreadName + " set age to:" + age);
        //獲取一個Student對象,並將隨機數年齡插入到對象屬性中
        Student student = getStudent();
        student.setAge(age);
        System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
        try {
            Thread.sleep(500);
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
    }
 
    protected Student getStudent() {
        //獲取本地線程變量並強制轉換爲Student類型
        Student student = (Student) studentLocal.get();
        //線程首次執行此方法的時候,studentLocal.get()確定爲null
        if (student == null) {
            //建立一個Student對象,並保存到本地線程變量studentLocal中
            student = new Student();
            studentLocal.set(student);
        }
        return student;
    }
}



結果:
a is running! 
thread a set age to:76 
b is running! 
thread b set age to:27 
thread a first read age is:76 
thread b first read age is:27 
thread a second read age is:76 
thread b second read age is:27
相關文章
相關標籤/搜索