ThreadLocal類的源碼分析與應用場景

前言

在實習的過程中項目有用到TreadLocal類,主要就是在日誌控制中,通過一個bean->RequestLog自定義類來保存request請求的參數,然後在控制檯中打印這些請求參數。在封裝請求的參數bean中可以維護一個靜態的TreadLocal對象,將每一次請求也就是不同的線程維護自己線程內的一個requestLog實例對象,封裝不同的request對象中的請求參數,然後在控制檯打印這些參數日誌,實現日誌記錄。

本文將通過簡單分析源碼的過程來加深對ThreadLocal類的理解,方便日後使用

ThreadLocal部分源碼

這裏主要介紹了get和set方法,因爲一般的項目應用中用的最多的也就保存和獲取值了,如果用到其他的方法,我再添加進來。

先來看看set(T value)方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

解析下set方法:
1.首先會獲取當前正在運行的線程
2.通過getMap獲取當前線程實例下的map集合ThreadLocal.ThreadLocalMap
3.判斷該map是否爲null
3.1如果不爲null,就直接map.set(this,value)當前threadLocal實例爲key
3.2如果爲null,就new ThreadLOcalMap(this,firstValue),會做一些初始化entry數組的工作。

第一步很簡單,獲取當前線程,第二部getMap的源碼如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

其實是TreadLocal類的內部維護了一個靜態的內部類ThreadLocalMap類。然後上面的方法返回t.threadLocals.其實是線程類Tread中的成員變量。我們再來看看Thread類的部分源碼:

public Class Thread{
ThreadLocal.ThreadLocalMap threadLocals = null;
}

也就是說每個線程都維護了自己實例對象中的map集合,將作爲set方法值得容器保存在各自的線程中。
第三步中 map.set(this, value);其實this值得是當前TreadLocal實例,一般在實際應用中都會維護一個靜態同一個TreadLocal作爲map的key,值作爲需要保存的值保存到map中。
如果map爲null,那麼會創建一個初始化map, createMap(t, value);跟hashMap實現類似,內部會維護一個entry,數組 ,初始化數組的大小等操作。源碼如下:

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

下面來看一看get()方法的部分源碼:

public T get() {
    Thread t = Thread.currentThread();
    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();
}

解析下get方法:
1.首先會先獲取當前線程。
2.獲取當前線程的map。
3.判斷map是否爲null
3.1如果不爲null,獲取map的entry(this),根據當前的threadLocal實例作爲key,獲取entry,然後e.value獲取值,返回。
3.2如果map爲null,return 初始化value的值。
3.2.1再次獲取map,判斷不是null,設置map(this,null);
3.2.2爲null,new map(this,null);
3.3最後返回null;

主要看一下map爲null的情況吧,返回null:

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

  protected T initialValue() {
        return null;
    }

相信看完了應該都會明白的,這裏不再詳細說明了。不明白的可以私信我。

ThreadLocal類總結

首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。

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

如果ThreadLocal.set()進去的東西本來就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有併發訪問問題。

其實說了這麼多歸納一下也就兩點:

1。每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。
2。將一個共用的ThreadLocal靜態實例作爲key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然後在線程執行的各處通過這個靜態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,再將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類來維護不用的請求線程保存各自的request請求參數,然後進行日誌的打印。可以自行實現!

發佈了65 篇原創文章 · 獲贊 67 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章