ThreadLocal如何实现?
這是我上周的帖子的后續(xù)文章,在那篇文章中我解釋了ThreadLocal用法的動(dòng)機(jī) 。 從帖子中我們可以回想起,如果您希望每個(gè)線程都有一個(gè)獨(dú)立初始化的變量副本,則ThreadLocal確實(shí)是一個(gè)很酷的概念。 現(xiàn)在,好奇的人可能已經(jīng)開始問“我如何在Java中實(shí)現(xiàn)這樣的概念”?
否則,您可能會(huì)覺得這不是一個(gè)有趣的話題–畢竟,您在這里所需的只是一張地圖 ,不是嗎? 在處理ThreadLocal <T>時(shí) ,將解決方案實(shí)現(xiàn)為HashMap <Thread,T>并以Thread.currentThread()作為鍵似乎是明智的。 其實(shí)不是那么簡單。 因此,如果您有五分鐘的時(shí)間,請(qǐng)忍受我,我將指導(dǎo)您完成一個(gè)美麗的設(shè)計(jì)概念。
簡單的HashMap解決方案的第一個(gè)明顯問題是線程安全性。 由于HashMap并不是為支持并發(fā)使用而構(gòu)建的,因此我們無法在多線程環(huán)境中安全地使用該實(shí)現(xiàn)。 幸運(yùn)的是,我們不需要花很多時(shí)間來解決問題-ConcurrentHashMap <Thread,T>看起來像是天作之合。 檢索的完全并發(fā)性和可調(diào)整的預(yù)期更新并發(fā)性正是我們首先需要的。
現(xiàn)在,如果您將基于ConcurrentHashMap的解決方案應(yīng)用于JDK源代碼中的ThreadLocal實(shí)現(xiàn),則會(huì)帶來兩個(gè)嚴(yán)重的問題。
- 首先,在Map結(jié)構(gòu)中將線程作為鍵。 由于映射永遠(yuǎn)不會(huì)被垃圾回收,因此最終您將永遠(yuǎn)保持對(duì)Thread的引用,從而阻止了該線程成為GCd。 不情愿的是,您在設(shè)計(jì)中造成了巨大的內(nèi)存泄漏。
- 第二個(gè)問題可能需要更長的時(shí)間才能浮出水面,但是即使在幕后進(jìn)行了巧妙的分段以減少鎖爭用的機(jī)會(huì), ConcurrentHashMap仍然承擔(dān)同步開銷。 在同步需求仍然存在的情況下,您仍然擁有一個(gè)結(jié)構(gòu),可能成為瓶頸的根源。
但是,讓我們首先開始解決最大的問題。 如果我們的引用是指向相關(guān)線程的最后一個(gè)引用,則我們的數(shù)據(jù)結(jié)構(gòu)需要允許對(duì)線程進(jìn)行垃圾回收。 再次,第一個(gè)可能的解決方案是盯著我們–而不是通常引用對(duì)象,為什么不使用WeakReferences ? 因此,實(shí)現(xiàn)現(xiàn)在看起來類似于以下內(nèi)容:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())現(xiàn)在,我們已經(jīng)解決了泄漏問題–如果除我們之外沒有人引用Thread ,則可以將其定型并進(jìn)行垃圾回收。 但是我們?nèi)匀粵]有解決并發(fā)問題。 現(xiàn)在,解決方案實(shí)際上是關(guān)于跳出思路思考的樣本。 到目前為止,我們已經(jīng)將ThreadLocal變量視為映射到變量的Threads 。 但是,如果我們顛倒了思路,而是設(shè)想了一個(gè)解決方案,將ThreadLocal對(duì)象映射到每個(gè)Thread中的值,該怎么辦? 如果每個(gè)線程都存儲(chǔ)該映射,并且ThreadLocal只是該映射的接口,則可以避免同步問題。 更好的是,我們還避免了GC帶來的問題!
確實(shí),當(dāng)我們打開ThreadLocal和Thread類的源代碼時(shí) ,我們看到這正是在JDK中實(shí)際實(shí)現(xiàn)該解決方案的方式:
public class Thread implements Runnable {ThreadLocal.ThreadLocalMap threadLocals = null;// cut for brevity }public class ThreadLocal<T> {static class ThreadLocalMap {// cut for brevity}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}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();}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;}// cut for brevity }因此,這里有它。 Thread類保留對(duì)ThreadLocal.ThreadLocalMap實(shí)例的引用,該實(shí)例是使用對(duì)鍵的弱引用構(gòu)建的。 以相反的方式構(gòu)建結(jié)構(gòu),因?yàn)槲覀兊腡hreadLocal只能訪問當(dāng)前線程中的值,所以我們完全避免了線程爭用問題。 另外,當(dāng)Thread完成工作時(shí),映射可以進(jìn)行垃圾回收,因此我們也避免了內(nèi)存泄漏的問題。
希望您對(duì)設(shè)計(jì)有所了解,因?yàn)樗_實(shí)是解決復(fù)雜問題的理想解決方案。 我確實(shí)認(rèn)為閱讀源代碼是學(xué)習(xí)新概念的理想方式。 而且,如果您是Java開發(fā)人員,那么與閱讀集成到JDK的Joshua Bloch和Doug Lea源代碼相比,還有什么比這更好的獲取知識(shí)的地方了?
翻譯自: https://www.javacodegeeks.com/2013/11/how-is-threadlocal-implemented.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的ThreadLocal如何实现?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Java的RESTful Web服务
- 下一篇: 向您的JVM添加一些熵