摘要:將當前線程局部變量的值刪除。是調用當期線程,返回當前線程中的一個成員變量。的一個使用示例測試類啟動兩個線程,第一個線程中存儲的為,第二個線程中存儲的為。
ThreadLocal是什么?
ThreadLocal 源碼解釋:
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. ThreadLocal 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修飾。
簡單地說,ThreadLocal的作用就是為每一個線程提供了一個獨立的變量副本,每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
我們必須要區分ThreadLocal和Syncronized這種同步機制,兩者面向的問題領域是不一樣的。sysnchronized是一種互斥同步機制,是為了保證在多線程環境下對于共享資源的正確訪問。而ThreadLocal從本質上講,無非是提供了一個“線程級”的變量作用域,它是一種線程封閉(每個線程獨享變量)技術,更直白點講,ThreadLocal可以理解為將對象的作用范圍限制在一個線程上下文中,使得變量的作用域為“線程級”。
沒有ThreadLocal的時候,一個線程在其聲明周期內,可能穿過多個層級,多個方法,如果有個對象需要在此線程周期內多次調用,且是跨層級的(線程內共享),通常的做法是通過參數進行傳遞;而ThreadLocal將變量綁定在線程上,在一個線程周期內,無論“你身處何地”,只需通過其提供的get方法就可輕松獲取到對象。極大地提高了對于“線程級變量”的訪問便利性。
ThreadLocal中的方法在JDK1.5以后,ThreadLocal已經支持泛型,該類的類名已經變為ThreadLocal
ThreadLocal
void set(T value)設置當前線程的線程本地變量的值。
public T get()該方法返回當前線程所對應的線程局部變量。
public void remove()將當前線程局部變量的值刪除。
該方法是JDK 5.0新增的方法,目的是為了減少內存的占用。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。
protected T initialValue()返回該線程局部變量的初始值。
該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。
這個方法是一個延遲調用方法,在線程第1次調用get()或set(T value)時才執行,并且僅執行1次,ThreadLocal中的缺省實現是直接返回一個null。
可以通過上述的幾個方法實現ThreadLocal中變量的訪問,數據設置,初始化以及刪除局部變量。
注意,在JDK1.8版本中還多了如下的這個方法:
/** * Creates a thread local variable. The initial value of the variable is * determined by invoking the {@code get} method on the {@code Supplier}. * * @paramThreadLocal的原理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 staticThreadLocalwithInitial(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 * initialValue method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
上述是在ThreadLocal類中的幾個主要的方法,他們的核心都是對其內部類ThreadLocalMap進行操作,下面看一下該類的源代碼:
static class ThreadLocalMap { //map中的每個節點Entry,其鍵key是ThreadLocal并且還是弱引用,這也導致了后續會產生內存泄漏問題的原因。 static class Entry extends WeakReferenceThreadLocal的幾個問題 為什么不直接用線程id來作為ThreadLocalMap的key?> { 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,那就只有一個鍵值對了。
ThreadLocal的內存泄露問題首先要理解內存泄露(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。
ThreadLocal的一個使用示例 測試類:ThreadLocalTest.java啟動兩個線程,第一個線程中存儲的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(); } }OrderHandler.java
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()); } }OrderService.java
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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76437.html
摘要:方法,刪除當前線程綁定的這個副本數字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據參考資料所說,選擇這個數字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關于ThreadLocal常見的疑問,希望可以通過這篇學...
摘要:在方法中取出開始時間,并計算耗時。是一個數組主要用來保存具體的數據,是的大小,而這表示當中元素數量超過該值時,就會擴容。如果這個剛好就是當前對象,則直接修改該位置上對象的。 想要獲取更多文章可以訪問我的博客?-?代碼無止境。 什么是ThreadLocal ThreadLocal在《Java核心技術 卷一》中被稱作線程局部變量(PS:關注公眾號itweknow,回復Java核心技術獲取該...
摘要:通過將保存在中,每個線程都會擁有屬于自己的,代碼如下所示然后你就可以安心地調用了,不用考慮線程安全問題。這樣設計的好處就是,當線程死掉之后,沒有強引用,方便收集器回收。 前言 想必大家都對Threadlocal很熟悉吧,今天我們就一起來深入學習一下。Threadlocal我更傾向于將其翻譯成線程局部變量。它有什么用處呢?Threadlocal對象通常用于防止對可變的單實例變量或全局變量...
摘要:返回索引位置的值。因為依賴于靜態成員變量的關系,所以它的肯定唯一獲取當前線程。位置還沒有初始化第一次這個,直接將放到的位置。在線程池模式下,生命周期伴隨著線程一直存在,可能出現內存泄漏的情況,最好手動調用方法。 本文原創地址,:jsbintask的博客(食用效果最佳),轉載請注明出處! 前言 ThreadLocal是jdk中一個非常重要的工具,它可以控制堆內存中的對象只能被指定線程訪問,如...
摘要:而應用場景更多是想共享一個變量,但是該變量又不是線程安全的,那么可以用維護一個線程一個實例。因為創建這個對象本身很費時的,而且我們也知道本身不是線程安全的,也不能緩存一個共享的實例,為此我們想到使用來給每個線程緩存一個實例,提高性能。 很多人都知道java中有ThreadLocal這個類,但是知道ThreadLocal這個類具體有什么作用,然后適用什么樣的業務場景還是很少的。今天我就嘗...
摘要:但是還有另外的功能看的后一半代碼作用就是掃描位置之后的數組直到某一個為的位置,清除每個為的,所以使用可以降低內存泄漏的概率。 在涉及到多線程需要共享變量的時候,一般有兩種方法:其一就是使用互斥鎖,使得在每個時刻只能有一個線程訪問該變量,好處就是便于編碼(直接使用 synchronized 關鍵字進行同步訪問),缺點在于這增加了線程間的競爭,降低了效率;其二就是使用本文要講的 Threa...
閱讀 2550·2023-04-26 00:56
閱讀 2007·2021-10-25 09:46
閱讀 1240·2019-10-29 15:13
閱讀 815·2019-08-30 15:54
閱讀 2196·2019-08-29 17:10
閱讀 2617·2019-08-29 15:43
閱讀 501·2019-08-29 15:28
閱讀 3027·2019-08-29 13:24