ThreadLocal 源碼解釋:java
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. <tt>ThreadLocal</tt> instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).
大概的理解是:ThreadLocal類用來提供線程內部的本地變量。這些變量在每一個線程內會有一個獨立的初始化的副本,和普通的副本不一樣,每一個線程只能訪問本身的副本(經過get或set方法訪問)。在一個類裏邊ThreadLocal成員變量一般由private static修飾。web
簡單地說,ThreadLocal的做用就是爲每個線程提供了一個獨立的變量副本,每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。api
咱們必需要區分ThreadLocal和Syncronized這種同步機制,二者面向的問題領域是不同的。sysnchronized是一種互斥同步機制,是爲了保證在多線程環境下對於共享資源的正確訪問。而ThreadLocal從本質上講,無非是提供了一個「線程級」的變量做用域,它是一種線程封閉(每一個線程獨享變量)技術,更直白點講,ThreadLocal能夠理解爲將對象的做用範圍限制在一個線程上下文中,使得變量的做用域爲「線程級」。數組
沒有ThreadLocal的時候,一個線程在其聲明週期內,可能穿過多個層級,多個方法,若是有個對象須要在此線程週期內屢次調用,且是跨層級的(線程內共享),一般的作法是經過參數進行傳遞;而ThreadLocal將變量綁定在線程上,在一個線程週期內,不管「你身處何地」,只需經過其提供的get方法就可輕鬆獲取到對象。極大地提升了對於「線程級變量」的訪問便利性。tomcat
在JDK1.5之後,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>。
ThreadLocal<T>類提供了4個可用的方法(基於JDK1.7版本):多線程
能夠經過上述的幾個方法實現ThreadLocal中變量的訪問,數據設置,初始化以及刪除局部變量。less
注意,在JDK1.8版本中還多了以下的這個方法:ide
/** * Creates a thread local variable. The initial value of the variable is * determined by invoking the {@code get} method on the {@code Supplier}. * * @param <S> the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @return a new thread local variable * @throws NullPointerException if the specified supplier is null * @since 1.8 */ public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }
ThreadLocal內部是如何爲每個線程維護變量副本的呢?
在ThreadLocal類中有一個靜態內部類ThreadLocalMap(概念上相似於Map),用鍵值對的形式存儲每個線程的變量副本,ThreadLocalMap中元素的key爲當前ThreadLocal對象,而value對應線程的變量副本,每一個線程可能存在多個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(); //當前線程 ThreadLocalMap map = getMap(t); //獲取當前線程對應的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //獲取對應ThreadLocal的變量值 if (e != null) return (T)e.value; } return setInitialValue(); //若當前線程還未建立ThreadLocalMap,則返回調用此方法並在其中調用createMap方法進行建立並返回初始值。 } // 是調用當期線程t,返回當前線程t中的一個成員變量threadLocals。 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // java.lang.Thread類下, 實際上就是一個ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals = null; /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's 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); } /** * 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); } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
上述是在ThreadLocal類中的幾個主要的方法,他們的核心都是對其內部類ThreadLocalMap進行操做,下面看一下該類的源代碼:this
static class ThreadLocalMap { //map中的每一個節點Entry,其鍵key是ThreadLocal而且仍是弱引用,這也致使了後續會產生內存泄漏問題的緣由。 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } /** * 初始化容量爲16,覺得對其擴充也必須是2的指數 */ private static final int INITIAL_CAPACITY = 16; /** * 真正用於存儲線程的每一個ThreadLocal的數組,將ThreadLocal和其對應的值包裝爲一個Entry。 */ private Entry[] table; ///....其餘的方法和操做都和map的相似 }
這個問題很容易解釋,由於一個線程中能夠有多個ThreadLocal對象,因此ThreadLocalMap中能夠有多個鍵值對,存儲多個value值,而若是使用線程id做爲key,那就只有一個鍵值對了。
首先要理解內存泄露(memory leak)和內存溢出(out of memory)的區別。內存溢出是由於在內存中建立了大量在引用的對象,致使後續再申請內存時沒有足夠的內存空間供其使用。內存泄露是指程序申請完內存後,沒法釋放已申請的內存空間,(再也不使用的對象或者變量仍佔內存空間)。
根據上面Entry方法的源碼,咱們知道ThreadLocalMap是使用ThreadLocal的弱引用做爲Key的。下圖是本文介紹到的一些對象之間的引用關係圖,實線表示強引用,虛線表示弱引用:
如上圖,ThreadLocalMap使用ThreadLocal的弱引用做爲key,若是一個ThreadLocal沒有外部強引用引用他,那麼系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,若是當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠沒法回收,形成內存泄露。
只有當前thread結束之後, Thread Ref就不會存在棧中,強引用斷開, Thread, ThreadLocalMap, Entry將所有被GC回收。但若是是線程對象不被回收的狀況,好比使用線程池,線程結束是不會銷燬的,就可能出現真正意義上的內存泄露。
ThreadLocalMap設計時的對上面問題的對策:
當咱們仔細讀過ThreadLocalMap的源碼,咱們能夠推斷,若是在使用的ThreadLocal的過程當中,顯式地進行remove是個很好的編碼習慣,這樣是不會引發內存泄漏。
那麼若是沒有顯式地進行remove呢?只能說若是對應線程以後調用ThreadLocal的get和set方法都有很高的機率會順便清理掉無效對象,斷開value強引用,從而大對象被收集器回收。
但不管如何,咱們應該考慮到什麼時候調用ThreadLocal的remove方法。一個比較熟悉的場景就是對於一個請求一個線程的server如tomcat,在代碼中對web api做一個切面,存放一些如用戶名等用戶信息,在鏈接點方法結束後,再顯式調用remove。
啓動兩個線程,第一個線程中存儲的userid爲1,第二個線程中存儲的userid爲2。
package com.lzumetal.multithread.threadlocal; import java.math.BigDecimal; public class ThreadLocalTest { public static void main(String[] args) throws InterruptedException { Order order01 = new Order(1, 1, new BigDecimal(10), 1); new Thread(new OrderHandler(1, order01)).start(); Order order02 = new Order(2, 2, new BigDecimal(20), 2); new Thread(new OrderHandler(2, order02)).start(); } }
package com.lzumetal.multithread.threadlocal; public class OrderHandler implements Runnable { private static OrderService orderService = new OrderService(); private Integer userId; private Order order; public OrderHandler(Integer userId, Order order) { this.userId = userId; this.order = order; } @Override public void run() { EnvUtil.getUserIdContext().set(userId); orderService.addOrder(order); orderService.updateStock(order.getGoodId(), order.getGoodCount()); } }
package com.lzumetal.multithread.threadlocal; public class OrderService { /** * 新增訂單 * * @param order */ public void addOrder(Order order) { Integer userId = EnvUtil.getUserIdContext().get(); System.out.println(Thread.currentThread().getName() + "新增訂單服務中獲取用戶id-->" + userId); } /** * 更新庫存 * * @param goodId * @param goodCount */ public void updateStock(Integer goodId, Integer goodCount) { //雖然更新庫存不須要關注userId,可是在這裏也同樣可以獲取到 Integer userId = EnvUtil.getUserIdContext().get(); System.out.println(Thread.currentThread().getName() + "在更新庫存中獲取用戶id-->" + userId); } }
Thread-0新增訂單服務中獲取用戶id-->1 Thread-1新增訂單服務中獲取用戶id-->2 Thread-0在更新庫存中獲取用戶id-->1 Thread-1在更新庫存中獲取用戶id-->2