1. 程式人生 > >ThreadLocal類分析

ThreadLocal類分析

rep 性能 method ive 區別 throws 最終 view enabled

首先試想一個場景:

多個線程都要訪問數據庫,先要獲得一個Connection,然後執行一些操作。為了線程安全,如果用synchronized鎖定一個Connection對象,那麽任何時候,都只有一個線程能通過Connection對象操作數據庫。這樣的話,程序的效率太低。反過來,如果每次需要Connection對象就去new一個的話,就會同時存在數量龐大的數據庫連接,你受得了,數據庫受不了。於是就有人提出折中方案:為每個線程只生成一個Connection對象,這樣別的線程訪問不到這個對象,線程安全問題解決;而且無論線程有多少地方需要數據庫連接,都是在復用這個Connection對象,數據庫的壓力會小很多。

其實不僅僅是數據庫,其它的場景比如說,SimpleDateFormat。我們處理日期的時候,經常要用到這個類,但是這個類不是線程安全的,在多線程下是會出問題的。這時候,采用上述折中方案是比較合理的。

那麽如何實現這種折中方案呢?我們先動手試一試唄!!!

要確保某類型的變量,每個線程只有一份。因為每個線程的ID是唯一的,這是JVM保證的,所有我們可以定義一個Map:線程ID作為key,我們要用的變量作為value。

稍微對這個Map進行簡單的封裝,當做一個類來用:

package threadlocal;

import java.util.HashMap;
import java.util.Map;

public class ThreadLocalVar<T> { Map<Long, T> threadVarMap = new HashMap<Long, T>(); public T get() { return threadVarMap.get(Thread.currentThread().getId()); } public void set(T value) { threadVarMap.put(Thread.currentThread().getId(), value); } }

接下來,就把這個類扔到多線程環境裏面練一練

package threadlocal;

public class MyTest {
    ThreadLocalVar<Long> longLocal = new ThreadLocalVar<Long>();
    ThreadLocalVar<String> stringLocal = new ThreadLocalVar<String>();
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final MyTest test = new MyTest();
         
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
        for (int i=0; i<3; i++) { 
            Thread thread1 = new Thread(){
                public void run() {
                    test.set();
                    System.out.println(test.getLong());
                    System.out.println(test.getString());
                };
            };
            thread1.start();
            thread1.join();
        }
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

這個程序很簡單,看一遍就能明白具體邏輯。雖然都是調用的同一個對象test的getLong和getString方法,但是不同的線程獲取到的值不一樣。

運行結果:

技術分享
1
main
9
Thread-0
10
Thread-1
11
Thread-2
1
main
View Code

哈哈,我們就是使用了奇淫巧技,把一個對象簡單的get和set操作,轉到了對Map的get和set操作。如果光看MyTest這個類,再看結果,還是挺迷惑的吧。

這個時候就有人說了,Java的ThreadLocal機制,不是這麽實現的。對,也不對。JDK之前的老版本其實就是這麽實現來著,不過後來改了。為什麽改,且聽我慢慢道來。

先上一個真正的ThreadLocal版本的test程序:

package threadlocal;

public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
         
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        for (int i=0; i<3; i++) { 
            Thread thread1 = new Thread(){
                public void run() {
                    test.set();
                    System.out.println(test.getLong());
                    System.out.println(test.getString());
                };
            };
            thread1.start();
            thread1.join();
        }
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

和我們之前的test程序唯一的區別,就是使用了Java自帶的ThreadLocal類,那就進去看一看。

    /**
     * Returns the value in the current thread‘s copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread‘s value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        // 其實還是通過Map的數據結構
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

這是ThreadLocal的get方法,最終還是Map操作,但是這個Map以及Map裏面的Entry都是為ThreadLocal專門定制的,後面再說。看看getMap方法的邏輯

    /**
     * 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;
    }
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    // 定義在Thread類裏面
    ThreadLocal.ThreadLocalMap threadLocals = null;

從這裏能看出2點:

1、ThreadLocalMap這個Map是ThreadLocal的內部類

2、這個Map的持有者是Thread類,就是說每個線程都直接持有自己的Map

第2點跟我們之前的實現思路截然不同,我們定義的ThreadLocalVar類不被任何線程直接持有,只是獨立的第三方,保持各個線程的數據。

後面再詳細分析這裏為什麽要這麽實現。

先來看看ThreadLocal的內部類ThreadLocalMap的內部類Entry(別繞暈了)

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Entry繼承自弱引用,說明持有key的弱引用,而且key是ThreadLocal類型(跟之前的實現方式也截然不同)。

為了說明ThreadLocal的實現機制和類直接的關系,從網上盜一張圖,圖中實線是強引用,虛線是弱引用。

技術分享

每個線程持有Map有什麽好處?

1、線程消失,Map跟著消失,釋放了內存

2、保存數據的Map數量變多了,但是每個Map裏面Entry數量變少了。之前的實現裏面,每個Map裏面的Entry數量是線程的個數,現在是ThreadLocal的個數。熟悉Map數據結構的人都知道,這樣對Map的操作性能會提升。

至於為什麽要用弱引用,先來看看Entry類的註釋

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */

簡單來說,就是當ThreadLocal類型的key不再被引用時(值為null),對應的Entry能夠被刪除。

具體的實現就是,get操作會調用expungeStaleEntry,set操作會調用replaceStaleEntry,它們的效果就是遇到的key為null的Entry都會被刪除,那麽Entry內的value也就沒有強引用鏈,自然會被回收,防止內存泄露。這部分,請讀者仔細閱讀源碼。

經這麽一分析,是不是豁然開朗。

下面在看看ThreadLocal在一些框架裏面的應用:

1、Hibernate處理session,看看一個類ThreadLocalSessionContext

       private static final ThreadLocal<Map> CONTEXT_TL = new ThreadLocal<Map>();


       protected static Map sessionMap() {
        return CONTEXT_TL.get();
    }

    @SuppressWarnings({"unchecked"})
    private static void doBind(org.hibernate.Session session, SessionFactory factory) {
        Map sessionMap = sessionMap();
        if ( sessionMap == null ) {
            sessionMap = new HashMap();
            CONTEXT_TL.set( sessionMap );
        }
        sessionMap.put( factory, session );
    }

2、Spring處理事務,看看一個類TransactionSynchronizationManager

private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<String>("Current transaction name");

    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<Boolean>("Current transaction read-only status");

    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<Integer>("Current transaction isolation level");

    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<Boolean>("Actual transaction active");



public static void bindResource(Object key, Object value) throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Assert.notNull(value, "Value must not be null");
                // 處理ThreadLocal
        Map<Object, Object> map = resources.get();
        // set ThreadLocal Map if none found
        if (map == null) {
            map = new HashMap<Object, Object>();
                        // 處理ThreadLocal
            resources.set(map);
        }
        Object oldValue = map.put(actualKey, value);
        // Transparently suppress a ResourceHolder that was marked as void...
        if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
            oldValue = null;
        }
        if (oldValue != null) {
            throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
                    actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
                    Thread.currentThread().getName() + "]");
        }
    }

ThreadLocal類分析