Java ThreadLocal 使用详解
ThreadLocal的官方API解釋為:
"該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對應物,因為訪問某個變量(通過其?get?或?set?方法)的每個線程都有自己的局部變量,它獨立于變量的初始化副本。ThreadLocal?實例通常是類中的 private static 字段,它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。"
大概的意思有兩點:
應用場景
????ThreadLocal通常用來共享數據,當你想在多個方法中使用某個變量,這個變量是當前線程的狀態,其它線程不依賴這個變量,你第一時間想到的就是把變量定義在方法內部,然后再方法之間傳遞參數來使用,這個方法能解決問題,但是有個煩人的地方就是,每個方法都需要聲明形參,多處聲明,多處調用。影響代碼的美觀和維護。有沒有一種方法能將變量像private static形式來訪問呢?這樣在類的任何一處地方就都能使用。這個時候ThreadLocal大顯身手了。?
實踐
我們首先來看一段代碼
package com.test1;import java.util.HashMap; import java.util.Map;public class Test1 implements Runnable {private final static Map map = new HashMap();int id;/*static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>(){@Overrideprotected HashMap initialValue() {System.out.println(Thread.currentThread().getName()+"initialValue");return new HashMap();}};*/public Test1(int id) {super();this.id = id;}@Overridepublic void run() {//Map map = threadLocal.get();for (int i = 0; i < 20; i++) {map.put(i, i + id * 100);try {Thread.sleep(100);} catch (Exception ex) {}}System.out.println(Thread.currentThread().getName() + "# map.size()="+ map.size() + " # " + map);}public static void main(String[] args) {Thread[] runs = new Thread[15];Test1 t = new Test1(1);for (int i = 0; i < runs.length; i++) {runs[i] = new Thread(t);}for (int i = 0; i < runs.length; i++) {runs[i].start();}} }這段程序的本意是,啟動15個線程,線程向map中寫入20個整型值,然后輸出map。運行該程序,觀察結果,我們會發現,map中壓根就不止20個元素,這說明程序產生了線程安全問題。
我們都知道HashMap是非線程安全的,程序啟動了15個線程,他們共享了同一個map,15個線程都往map寫對象,這勢必引起線程安全問題。
我們有兩種方法解決這個問題:
實現原理
程序調用了get()方法,我們來看一下該方法的源碼:
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T)e.value;}return setInitialValue();}?getMap方法的源碼:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}該方法返回的是當前線程中的ThreadLocalMap實例。閱讀Thread的源碼我們發現Thread中有如下變量聲明:
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;我們暫時可以將ThreadLocalMap理解為一個類似Map的這么個類,之后再講解它。
get()方法的大致意思就是從當前線程中拿到ThreadLocalMap的實例threadLocals,如果threadLocals不為空,那么就以當前ThreadLocal實例為KEY從threadLocals中拿到對應的VALUE。如果不為空,那么就調用 setInitialValue()方法初始化threadLocals,最終返回的是initialValue()方法的返回值。下面是 setInitialValue()方法的源碼
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}我們看到map.set(this, value);這句代碼將ThreadLocalMap的實例作為KEY,將initialValue()的返回值作為VALUE,set到了threadLocals中。
程序在聲明ThreadLocal實例的時候覆寫了initialValue(),返回了VALUE,當然我們可以直接調用set(T t)方法來設置VALUE。下面是set(T t)方法的源碼:
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}我們看到它比setInitialValue()方法就少了個return語句。這兩種方式都能達到初始化ThreadLocalMap實例的效果。
我們再來看一下ThreadLocal類的結構。
ThreadLocal類只有三個屬性,如下:
/*ThreadLocal的hash值,map用它來存儲值*/private final int threadLocalHashCode = nextHashCode();/*改類能以原子的方式更新int值,這里主要是在產生新的ThreadLocal實例時用來產生一個新的hash值,map用該值來存儲對象*/private static AtomicInteger nextHashCode =new AtomicInteger();/*該變量標識每次產生新的ThreadLocal實例時,hash值的增量*/private static final int HASH_INCREMENT = 0x61c88647;剩下的就是一些方法。最關鍵的地方就是ThreadLocal定義了一個靜態內部類ThreadLocalMap。我們在下一章節再來分析這個類。從ThreadLocal的類結構,我們可以看到,實際上問題的關鍵先生是ThreadLocalMap,ThreadLocal只是提供了管理的功能,我們也可以說ThreadLocal只是代理了ThreadLocalMap而已。
ThreadLocalMap源碼分析
既然ThreadLocalMap實現了類似map的功能,那我們首先來看看它的set方法源碼:
private void set(ThreadLocal key, Object value) {// We don’t use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}這個方法的主要功能就是講KEY-VALUE存儲到ThreadLocalMap中,這里至少我們看到KEY實際上是 key.threadLocalHashCode,ThreadLocalMap同樣維護著Entry數組,這個Entry我們在下一節會講解。這里涉及到了Hash沖突的處理,這里并不會向HashMap一樣沖突了以鏈表的形式往后添加。如果對這個Hash沖突解決方案有興趣,可以再進一步研究源碼。
既然ThreadLocalMap也是用Entry來存儲對象,那我們來看看Entry類的聲明,Entry被定義在ThreadLocalMap的內部:
static class Entry extends WeakReference<ThreadLocal> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal k, Object v) {super(k);value = v;}}這里我們看到Entry集成了WeakReference類,泛型聲明了ThreadLocal,即每一個Entry對象都保留了對 ThreadLocal實例的弱引用,之所以這么干的原因是,線程在結束之后需要將ThreadLocal實例從map中remove調,以便回收內存空間。
總結
首先,ThreadLocalMap并不是為了解決線程安全問題,而是提供了一種將實例綁定到當前線程的機制,類似于隔離的效果,實際上自己在方法中new出來變量也能達到類似的效果。ThreadLocalMap跟線程安全基本不搭邊,綁定上去的實例也不是多線程公用的,而是每個線程new一份,這個實例肯定不是共用的,如果共用了,那就會引發線程安全問題。ThreadLocalMap最大的用處就是用來把實例變量共享成全局變量,在程序的任何方法中都可以訪問到該實例變量而已。網上很多人說ThreadLocalMap是解決了線程安全問題,其實是望文生義,兩者不是同類問題。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Java ThreadLocal 使用详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL ERROR 1045 (28
- 下一篇: java美元兑换,(Java实现) 美元