ThreadLocal原理分析與使用場景

cnblogs.com/luxiaoxun/p/8744826.html

什麼是ThreadLocal變量

ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本。這裡有幾點需要注意:

  • 因為每個 Thread 內有自己的實例副本,且該副本只能由當前 Thread 使用。這是也是 ThreadLocal 命名的由來。
  • 既然每個 Thread 有自己的實例副本,且其它 Thread 不可訪問,那就不存在多線程間共享的問題。

ThreadLocal 提供了線程本地的實例。它與普通變量的區別在於,每個使用該變量的線程都會初始化一個完全獨立的實例副本。ThreadLocal 變量通常被private static修飾。當一個線程結束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收。

總的來說,ThreadLocal 適用於每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。

ThreadLocal實現原理

首先 ThreadLocal 是一個泛型類,保證可以接受任何類型的對象。

因為一個線程內可以存在多個 ThreadLocal 對象,所以其實是 ThreadLocal 內部維護了一個 Map ,這個 Map 不是直接使用的 HashMap ,而是 ThreadLocal 實現的一個叫做 ThreadLocalMap 的靜態內部類。

而我們使用的 get()、set() 方法其實都是調用了這個ThreadLocalMap類對應的 get()、set() 方法。例如下面的 set 方法:


ThreadLocal原理分析與使用場景

createMap方法:

<code>    void createMap(Thread t, T firstValue) {   
        t.threadLocals = new ThreadLocalMap(this, firstValue);   
    } /<code>

ThreadLocalMap是個靜態的內部類:

<code>    static class ThreadLocalMap {   
    ........   
    }  /<code>

最終的變量是放在了當前線程的 ThreadLocalMap 中,並不是存在 ThreadLocal 上,ThreadLocal 可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。

內存洩漏問題

實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點是,如果這個對象只存在弱引用,那麼在下一次垃圾回收的時候必然會被清理掉。

所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現 key 為 null 的 value。

ThreadLocalMap實現中已經考慮了這種情況,在調用 set()、get()、remove() 方法的時候,會清理掉 key 為 null 的記錄。如果說會出現內存洩漏,那只有在出現了 key 為 null 的記錄後,沒有手動調用 remove() 方法,並且之後也不再調用 get()、set()、remove() 方法的情況下。

使用場景

如上文所述,ThreadLocal 適用於如下兩種場景

  • 每個線程需要有自己單獨的實例
  • 實例需要在多個方法中共享,但不希望被多線程共享

對於第一點,每個線程擁有自己實例,實現它的方式很多。例如可以在線程內部構建一個單獨的實例。ThreadLoca 可以以非常方便的形式滿足該需求。

對於第二點,可以在滿足第一點(每個線程有自己的實例)的條件下,通過方法間引用傳遞的形式實現。ThreadLocal 使得代碼耦合度更低,且實現更優雅。擴展:來探討一下最近面試問的ThreadLocal問題

1)存儲用戶Session

一個簡單的用ThreadLocal來存儲Session的例子:

<code>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;
    }/<code>

2)解決線程安全的問題

比如Java7中的SimpleDateFormat不是線程安全的,可以用ThreadLocal來解決這個問題:

<code>public class DateUtil {
    private static ThreadLocal<simpledateformat> format1 = new ThreadLocal<simpledateformat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static String formatDate(Date date) {
        return format1.get().format(date);
    }
}/<simpledateformat>/<simpledateformat>/<code>

這裡的DateUtil.formatDate()就是線程安全的了。(Java8裡的 java.time.format.DateTimeFormatter是線程安全的,Joda time裡的DateTimeFormat也是線程安全的)。


分享到:


相關文章: