<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
因此本文主要結合常見的一些疑問、ThreadLocal
原始碼、應用範例以注意事項來全面而深入地再詳細講解一遍ThreadLocal
。希望大家看完本文後可以徹底掌握ThreadLocal
。
在闡述ThreadLocal
之前,我們先來看下它的設計者是怎麼描述ThreadLocal
的吧。
看完官方的描述後,結合自己的理解,ThreadLocal
提供了一種對應獨立執行緒內的資料存取機制,實現了變數線上程之間隔離,線上程生命週期內獨立獲取或者設定的能力。如果我們想線上程內傳遞引數但是有不想作為方法引數的時候,ThreadLocal
就可以排上用場了。不過值得注意的是ThreadLocal
並不會解決變數共用問題。實際上從ThreadLocal
的名稱上面來看,執行緒本地變數也已經大致說明了它的作用,所以變數的命名還是非常重要的,要做到顧名思義。如果覺得還不是很理解,沒關係,我們可以通過以下的場景再加深下理解。
假如有以下的場景,假設只有一個資料庫連線,使用者端1、2、3都需要獲取資料庫連線來進行具體的資料庫操作,但是同一時間點只能有一個執行緒獲取連線,其他執行緒只能等待。因此就會出現資料庫存取效率不高的問題。
那我們有沒有什麼辦法能夠避免執行緒等待的情況呢?上述問題的根本原因是資料庫連線是共用變數,同事只能有一個執行緒可以進行操作。那如果三個執行緒都有自己的資料庫連線,互相隔離,那不就不會出現等待的問題了嘛。那麼此時我麼可以使用ThreadLocal
實現在不同執行緒中的變數隔離。可以看出來,ThreadLocal
是一種已空間換取時間的做法。
從上文中,我們瞭解到ThreadLocal
可以實現變數存取的執行緒級別的隔離。那麼它是到底如何實現的呢?這還需要結合Thread
以及ThreadLocal
的原始碼來分析才能揭開ThreadLocal
實現執行緒隔離的神祕面紗。
public class Thread implements Runnable { ... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ... }
在Thread
原始碼中我們發現,它有一個threadLocals
變數,它的型別是ThreadLocal中的內部類ThreadLocalMap
。我們在看下ThreadLocalMap
的定義是怎樣的。從原始碼中我們可以看出來,ThreadLocalMap
實際上就是Entry
陣列,這個Entry
對應的key
實際就是ThreadLocal
的範例,value
就是實際的變數值。
public class ThreadLocal<T> { ... static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... //底層資料結構是陣列 private Entry[] table; ... } ... }
通過檢視上述的原始碼,如果還不太好理解的話,我們再結合下現實中的例子來理解。大家都有支付寶賬戶,我們通過它來管理著我們的銀行卡、餘額、花唄這些金融服務。
我們以支付寶以及支付寶賬戶進行類比,假設ThreadLocal
就是支付寶,每個支付寶賬戶實際就是單獨的執行緒,而賬戶中的餘額屬性就相當於Thread
的私有屬性ThreadLocalMap
。我們在日常生活中,進行賬戶餘額的充值或者消費,並不是直接通過賬戶進行操作的,而是藉助於支付寶進行維護的。這就相當於每個執行緒對ThreadLocalMap
進行操作的時候也不是直接操作的,而是藉助於ThreadLocal
來操作。
那麼Thread
到底是怎麼藉助ThreadLocal
進行私有屬性管理的呢?還是需要進一步檢視Thread
進行set
以及get
操作的原始碼。從以下的ThreadLocal
的原始碼中我們可以看出,在進行操作之前,需要獲取當前的執行操作的執行緒,再根據執行緒或者執行緒中私有的ThreadLocalMap
屬性來進行操作。
在進行資料獲取的時候,也是按照同樣的流程,先獲取當前的執行緒,再獲取執行緒中對應的ThreadLocalMap
屬性來進行後續的值的獲取。
經過上述的原始碼的分析,我們可以得出這樣的結論,ThreadLocal
之所以可以實現變數的執行緒隔離存取,實際上就是藉助於Thread
中的ThreadLocalMap
屬性來進行操作。由於都是操作執行緒本身的屬性,因此並不會影響其他執行緒中的變數值,因此可以實現執行緒級別的資料修改隔離。
我們都知道,ThreadLocal
如果使用不當的話會出現記憶體漏失的問題,那麼我們就通過下面的這段程式碼來分析下,記憶體漏失的原因到底是什麼。
/** * @author mufeng * @description 測試ThreadLocal記憶體溢位 * @date 2022/1/16 19:01 * @since */ public class ThreadLocalOOM { /** * 測試執行緒池 */ private static Executor threadPool = new ThreadPoolExecutor(3, 3, 40, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); static class Info { private byte[] info = new byte[10 * 1024 * 1024]; } private static ThreadLocal<Info> infoThreadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { threadPool.execute(() -> { infoThreadLocal.set(new Info()); System.out.println("Thread started:" + Thread.currentThread().getName()); }); Thread.sleep(100); } } }
手動進行GC
之後,我們可以發現堆中仍然有超過30M的堆記憶體佔用,如上面的程式碼,線上程池中活躍的執行緒會有三個,對應的value
為10M,說明線上程還存活的情況下,對應的value
並沒有被回收,因此存在記憶體漏失的情況,如果存在大量執行緒的情況,就會出現OOM
。
當我們修改程式碼線上程中進行remove
操作,手動GC之後我們發現堆記憶體趨近於0了,之前沒有被回收的物件已經被回收了。
以上是對於ThreadLocal
發生記憶體漏失問題的演示,那麼再來仔細分析下背後的原因是什麼。ThreadLocal
中實際儲存資料的是ThreadLocalMap
,實際上Map
對應的key
是一個虛參照,在GC
的時候可以被回收掉,但是問題就在於key所對應的value
,它是強參照,只要執行緒存活,那麼這條參照鏈就會一致存在,如果出現大量執行緒的時候就會有OOM
的風險。 所以在使用ThreadLocal
的時候一定記得要顯式的呼叫remove
方法進行清理,防止記憶體漏失。
到這裡,我相信大家對於ThreadLocal
的原理有了比較深入的理解了。結合上文中的ThreadLocal
程式碼,不知道大家有沒有思考過一個問題,我們在使用ThreadLocal
的時候都是在同一個執行緒內進行了set
以及get
操作,那麼如果set
操作與get
操作在父子執行緒中是否還可以正常的獲取呢?帶著這樣的疑問,我們來看下如下的程式碼。
/** * @author mufeng * @description 父子執行緒引數傳遞 * @date 2022/1/16 9:54 * @since */ public class InheritableThreadLocalMain { private static final ThreadLocal<String> count = new ThreadLocal<>(); public static void main(String[] args) { count.set("父子執行緒引數傳遞!!!"); System.out.println(Thread.currentThread().getName() + ":" + count.get()); new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + count.get()); }).start(); } }
與之前程式碼有所不同,ThreadLocal的設值是在main執行緒中進行的,但是獲取操作實際是在主執行緒下的子執行緒中進行的,大家可以分析一下執行結果是怎麼樣的。
看到這個執行結果,不知道大家分析的對不對呢。實際上如果理解了上文的核心的話,這個問題應該很好分析的。ThreadLocal
獲取資料的時候,首先是需要獲取當前的執行緒的,根據執行緒獲取實際儲存資料的ThreadLocalMap
,上文程式碼中設定和獲取在父子執行緒中進行,那肯定是獲取不到設定的資料的。但是在現實的專案開發中,我們會經常遇到需要將父執行緒的變數值傳遞給子執行緒進行處理,那麼應該要怎麼來實現呢?這個時候InheritableThreadLocal
就派上用場了。
/** * @author mufeng * @description 父子執行緒引數傳遞 * @date 2022/1/16 9:54 * @since */ public class InheritableThreadLocalMain { private static final ThreadLocal<String> count = new InheritableThreadLocal<>(); public static void main(String[] args) { count.set("父子執行緒引數傳遞!!!"); System.out.println(Thread.currentThread().getName() + ":" + count.get()); new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + count.get()); }).start(); } }
那麼InheritableThreadLocal
到底是如何實現父子執行緒的引數傳遞的呢?我麼還是的看看原始碼中的實現原理。實際上在Thread
原始碼中,除了有Threadlocal
私有屬性還有InheritableThreadLocal
私有屬性。
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ... public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ... //關鍵 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ... } ... }
實際在進行子執行緒建立的時候,線上程初始化過程中,判斷了父執行緒中的inheritableThreadLocals
屬性是否為空,如果不為空的話需要進行值的複製,這樣便實現了父子執行緒的值傳遞。
本文主要對ThreadLocal
進行了相對全面的分析,從它的使用場景、原理以及原始碼分析、產生OOM
的原因以及一些使用上的注意,相信通過本文的學習,大家對於ThreadLocal
會有更加深刻的理解。
到此這篇關於JDK原始碼白話解讀之ThreadLocal篇的文章就介紹到這了,更多相關Java ThreadLocal內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45