[轉]ThreadLocal使用

引言

ThreadLocal的官方API解釋爲:java

「該類提供了線程局部 (thread-local) 變量。這些變量不一樣於它們的普通對應物,由於訪問某個變量(經過其 get 或 set 方法)的每一個線程都有本身的局部變量,它獨立於變量的初始化副本。ThreadLocal 實例一般是類中的 private static 字段,它們但願將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。」編程

大概的意思有兩點:數組

  1. ThreadLocal提供了一種訪問某個變量的特殊方式:訪問到的變量屬於當前線程,即保證每一個線程的變量不同,而同一個線程在任何地方拿到的變量都是一致的,這就是所謂的線程隔離。
  2. 若是要使用ThreadLocal,一般定義爲private static類型,在我看來最好是定義爲private static final類型。

應用場景


 

ThreadLocal一般用來共享數據,當你想在多個方法中使用某個變量,這個變量是當前線程的狀態,其它線程不依賴這個變量,你第一時間想到的就是把變量定義在方法內部,而後再方法之間傳遞參數來使用,這個方法能解決問題,可是有個煩人的地方就是,每一個方法都須要聲明形參,多處聲明,多處調用。影響代碼的美觀和維護。有沒有一種方法能將變量像private static形式來訪問呢?這樣在類的任何一處地方就都能使用。這個時候ThreadLocal大顯身手了。安全

實踐


 

  1. 咱們首先來看一段代碼:

import java.util.HashMap;多線程

import java.util.Map;併發

 

public class TreadLocalTest {ide

// static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>(){源碼分析

// @Override性能

// protected HashMap initialValue() {this

// System.out.println(Thread.currentThread().getName()+」initialValue」);

// return new HashMap();

// }

// };

 

public static class T1 implements Runnable {

private final static Map map = new HashMap();

int id;

 

public T1(int id) {

this.id = id;

}

public 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];

T1 t = new T1(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寫對象,這勢必引發線程安全問題。

咱們有兩種方法解決這個問題:

  1. 將map的聲明放到run方法中,這樣map就成了方法內部變量,每一個線程都有一份new HashMap(),不管多少個線程執行run方法,都不會有線程安全問題。這個方法也正如應用場景中提到的,若是有多處地方使用到map,傳值是個煩人的地方。
  2. 將HashMap換成Hashtable。用線程同步來解決問題,然而咱們的程序只是想向一個map中寫入20個整型的KEY-VALUE而已,並不須要線程同步,同步勢必影響性能,得不償失。
  3. ThreadLocal提供另一種解決方案,即在解決方案a上邊,將new HashMap()獲得的實例變量,綁定到當前線程中。以後從任何地方,均可以經過ThreadLocal獲取到該變量。將程序中的註釋代碼恢復,再將 private final static Map map = new HashMap();註釋掉,運行程序,結果就是咱們想要的。

實現原理


 

  1. 程序調用了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);

else

createMap(t, value);

return value;

}

咱們看到map.set(this, value);這句代碼將ThreadLocalMap的實例做爲KEY,將initialValue()的返回值做爲VALUE,set到了threadLocals中。

  1. 程序在聲明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);

else

createMap(t, value);

}

咱們看到它比setInitialValue()方法就少了個return語句。這兩種方式都能達到初始化ThreadLocalMap實例的效果。

  1. 咱們再來看一下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源碼分析


 

  1. 既然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衝突解決方案有興趣,能夠再進一步研究源碼。

  1. 既然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是解決了線程安全問題,實際上是望文生義,二者不是同類問題。

 

轉載自併發編程網 – ifeve.com本文連接地址: ThreadLocal使用

相關文章
相關標籤/搜索