天天看點

Java ThreadLocal 使用詳解

引言

“該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對應物,因為通路某個變量(通過其 get 或 set 方法)的每個線程都有自己的局部變量,它獨立于變量的初始化副 本。threadlocal 執行個體通常是類中的 private static 字段,它們希望将狀态與某一個線程(例如,使用者 id 或事務 id)相關聯。”
Java ThreadLocal 使用詳解

大概的意思有兩點:

threadlocal提供了一種通路某個變量的特殊方式:通路到的變量屬于目前線程,即保證每個線程的變量不一樣,而同一個線程在任何地方拿到的變量都是一緻的,這就是所謂的線程隔離。

如果要使用threadlocal,通常定義為private static類型,在我看來最好是定義為private static final類型。

應用場景

threadlocal通常用來共享資料,當你想在多個方法中使用某個變量,這個變量是目前線程的狀态,其它線程不依賴這個變量,你第一時間想到的

就是把變量定義在方法内部,然後再方法之間傳遞參數來使用,這個方法能解決問題,但是有個煩人的地方就是,每個方法都需要聲明形參,多處聲明,多處調用。

影響代碼的美觀和維護。有沒有一種方法能将變量像private

static形式來通路呢?這樣在類的任何一處地方就都能使用。這個時候threadlocal大顯身手了。

實踐

我們首先來看一段代碼:

mport java.util.hashmap; 

import java.util.map; 

public class treadlocaltest { 

// static threadlocal<hashmap> threadlocal = new threadlocal<hashmap>(){ 

// @override 

// protected hashmap initialvalue() { 

// 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); 

runs[i].start(); 

這段程式的本意是,啟動15個線程,線程向map中寫入20個整型值,然後輸出map。運作該程式,觀察結果,我們會發現,map中壓根就不止20個元素,這說明程式産生了線程安全問題。

我們都知道hashmap是非線程安全的,程式啟動了15個線程,他們共享了同一個map,15個線程都往map寫對象,這勢必引起線程安全問題。

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

将map的聲明放到run方法中,這樣map就成了方法内部變量,每個線程都有一份new hashmap(),無論多少個線程執行run方法,都不會有線程安全問題。這個方法也正如應用場景中提到的,如果有多處地方使用到map,傳值是個煩人的地方。

将hashmap換成hashtable。用線程同步來解決問題,然而我們的程式隻是想向一個map中寫入20個整型的key-value而已,并不需要線程同步,同步勢必影響性能,得不償失。

threadlocal提供另外一種解決方案,即在解決方案a上邊,将new

hashmap()得到的執行個體變量,綁定到目前線程中。之後從任何地方,都可以通過threadlocal擷取到該變量。将程式中的注釋代碼恢複,再将

private final static map map = new hashmap();注釋掉,運作程式,結果就是我們想要的。

實作原理

程式調用了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(); 

if (map != null) 

map.set(this, value); 

else 

createmap(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) { 

我們看到它比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); 

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是解決了線程安全問題,其實是望文生義,兩者不是同類問題。

來源:51cto