什麼是ThreadLocal變量html
ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不一樣的 Thread 中有不一樣的副本。這裏有幾點須要注意:java
ThreadLocal 提供了線程本地的實例。它與普通變量的區別在於,每一個使用該變量的線程都會初始化一個徹底獨立的實例副本。ThreadLocal 變量一般被private static修飾。當一個線程結束時,它所使用的全部 ThreadLocal 相對的實例副本均可被回收。api
總的來講,ThreadLocal 適用於每一個線程須要本身獨立的實例且該實例須要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。安全
ThreadLocal實現原理多線程
首先 ThreadLocal 是一個泛型類,保證能夠接受任何類型的對象。oracle
由於一個線程內能夠存在多個 ThreadLocal 對象,因此實際上是 ThreadLocal 內部維護了一個 Map ,這個 Map 不是直接使用的 HashMap ,而是 ThreadLocal 實現的一個叫作 ThreadLocalMap 的靜態內部類。而咱們使用的 get()、set() 方法其實都是調用了這個ThreadLocalMap類對應的 get()、set() 方法。例以下面的 set 方法:app
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方法:ide
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; }
createMap方法:this
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap是個靜態的內部類:spa
static class ThreadLocalMap { ........ }
最終的變量是放在了當前線程的 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 使得代碼耦合度更低,且實現更優雅。
1)存儲用戶Session
一個簡單的用ThreadLocal來存儲Session的例子:
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; }
2)解決線程安全的問題
好比Java7中的SimpleDateFormat不是線程安全的,能夠用ThreadLocal來解決這個問題:
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); } }
這裏的DateUtil.formatDate()就是線程安全的了。(Java8裏的 java.time.format.DateTimeFormatter
是線程安全的,Joda time裏的DateTimeFormat也是線程安全的)。
參考:
http://www.jasongj.com/java/threadlocal/
When and how should I use a ThreadLocal variable?
http://java.jiderhamn.se/2012/01/29/classloader-leaks-iv-threadlocal-dangers-and-why-threadglobal-may-have-been-a-more-appropriate-name/