新聞中心
這篇文章運(yùn)用簡(jiǎn)單易懂的例子給大家介紹如何使用Java多線程之ThreadLocal,代碼非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
成都創(chuàng)新互聯(lián)是一家朝氣蓬勃的網(wǎng)站建設(shè)公司。公司專注于為企業(yè)提供信息化建設(shè)解決方案。從事網(wǎng)站開(kāi)發(fā),網(wǎng)站制作,網(wǎng)站設(shè)計(jì),網(wǎng)站模板,微信公眾號(hào)開(kāi)發(fā),軟件開(kāi)發(fā),成都小程序開(kāi)發(fā),10年建站對(duì)茶樓設(shè)計(jì)等多個(gè)領(lǐng)域,擁有豐富的網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn)。
在多線程環(huán)境下,訪問(wèn)非線程安全的變量時(shí)必須進(jìn)行線程同步,例如使用synchronized
方式訪問(wèn)HashMap
實(shí)例。但是同步訪問(wèn)會(huì)降低并發(fā)性,影響系統(tǒng)性能。這時(shí)候就可以用空間換時(shí)間,如果我們給每個(gè)線程都分配一個(gè)獨(dú)立的變量,就可以用非同步的方式使用非線程安全的變量,我們稱這種變量為線程局部變量。
顧名思義,線程局部變量是指每個(gè)線程都有一份屬于自己獨(dú)立的變量副本,不會(huì)像普通局部變量一樣可以被其他線程訪問(wèn)到。Java
并沒(méi)有提供語(yǔ)言級(jí)的線程局部變量,而是在類庫(kù)里提供了線程局部變量的功能,也就是這次的主角ThreadLocal
類。
ThreadLocal的使用
Java8版本的ThreadLocal有上圖所示的4個(gè)public方法和一個(gè)protected的方法,第一個(gè)方法用于返回初始值,默認(rèn)是null。第二個(gè)靜態(tài)方法withInitial(Supplier extends S> supplier)是Java8版本新添加的,后面三個(gè)實(shí)例方法則非常的簡(jiǎn)單。
在Java8之前,使用ThreadLocal時(shí)想要設(shè)置初始值時(shí)需要繼承ThreadLocal類覆蓋protected T initialValue()方法才行,例如:
ThreadLocalthreadLocal = new ThreadLocal () { @Override protected Integer initialValue() { return 0; } };
在Java8版本可以使用新添加的靜態(tài)方法withInitial(Supplier extends S> supplier),非常方便的設(shè)置初始值,例如:
ThreadLocalthreadLocal = ThreadLocal.withInitial(() -> 0); System.out.println(threadLocal.get()); threadLocal.set(16); System.out.println(threadLocal.get()); threadLocal.remove(); System.out.println(threadLocal.get()); // 同一個(gè)線程的輸出 0 16 0 Process finished with exit code 0
ThreadLocal的原理
那么ThreadLocal是怎么實(shí)現(xiàn)線程局部變量的功能的呢?其實(shí)ThreadLocal的基本原理并沒(méi)有十分復(fù)雜。ThreadLocal在內(nèi)部定義了一個(gè)靜態(tài)類ThreadLocalMap,ThreadLocalMap的鍵為ThreadLocal對(duì)象,ThreadLocalMap的值就是ThreadLocal存儲(chǔ)的值,不過(guò)這個(gè)ThreadLocalMap是在Thread類里維護(hù)的。我們來(lái)看一下ThreadLocal的部分源碼:
// ThreadLocal的set方法 public void set(T value) { // 獲取當(dāng)前線程對(duì)象 Thread t = Thread.currentThread(); // 獲取Map ThreadLocalMap map = getMap(t); if (map != null) // 設(shè)置值 map.set(this, value); else // 初始化Map createMap(t, value); } // ThreadLocal的createMap方法 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } // Thread類定義的實(shí)例域 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
可以看出ThreadLocal的核心實(shí)現(xiàn)就是ThreadLocalMap的實(shí)現(xiàn)了,ThreadLocalMap內(nèi)部聲明了一個(gè)Entry類來(lái)存儲(chǔ)數(shù)據(jù):
static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } }
ThreadLocalMap的實(shí)現(xiàn)與HashMap的實(shí)現(xiàn)有相似的地方,比如同樣是使用數(shù)組存儲(chǔ)數(shù)據(jù)和自動(dòng)擴(kuò)容,不同的是hash算法與hash碰撞后的處理不一樣。
// ThreadLocalMap的set方法 private void set(ThreadLocal> key, Object value) { Entry[] tab = table; int len = tab.length; // 計(jì)算在Entry[]中的索引,每個(gè)ThreadLocal對(duì)象都有一個(gè)hash值threadLocalHashCode,每初始化一個(gè)ThreadLocal對(duì)象,hash值就增加一個(gè)固定的大小0x61c88647 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; } // 代替無(wú)效的鍵 if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
可以看到ThreadLocalMap把Entry[]數(shù)組當(dāng)成一個(gè)圓環(huán)。從計(jì)算出來(lái)的索引位置開(kāi)始,如果該索引已經(jīng)有數(shù)據(jù)了就判斷Key是否相同,相同就更新值。否則就直到找到一個(gè)空的位置把值放進(jìn)去。獲取值的時(shí)候也類似,從計(jì)算出來(lái)的索引位置開(kāi)始一個(gè)一個(gè)檢查Key是否相同,這樣hash碰撞比較多的話可能性能就不是很好。
ThreadLocal的應(yīng)用
ThreadLocal的應(yīng)用是非常廣的,比如Java工程師非常熟悉的Spring框架中就使用了ThreadLocal來(lái)把非線程安全的狀態(tài)性對(duì)象封裝起來(lái),所以我們可以把絕大部分的Bean聲明為singleton作用域。我們?cè)诰帉懚嗑€程代碼時(shí)也可以想想是用同步的方式訪問(wèn)非線程安全的狀態(tài)性對(duì)象比較好,還是使用ThreadLocal把非線程安全的狀態(tài)性對(duì)象封裝起來(lái)更好。
關(guān)于如何使用Java多線程之ThreadLocal就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
分享標(biāo)題:如何使用Java多線程之ThreadLocal
分享網(wǎng)址:http://fisionsoft.com.cn/article/pdgedh.html