c++ thread 内存泄漏_使用 ThreadLocal如何避免内存泄露?
作者:魯毅
juejin.im/post/5e0d8765f265da5d332cde44
1.ThreadLocal的使用場(chǎng)景
1.1 場(chǎng)景1
每個(gè)線程需要一個(gè)獨(dú)享對(duì)象(通常是工具類,典型需要使用的類有SimpleDateFormat和Random)
每個(gè)Thread內(nèi)有自己的實(shí)例副本,不共享
比喻:教材只有一本,一起做筆記有線程安全問(wèn)題。復(fù)印后沒(méi)有問(wèn)題,使用ThradLocal相當(dāng)于復(fù)印了教材。
1.2 場(chǎng)景2
每個(gè)線程內(nèi)需要保存全局變量(例如在攔截器中獲取用戶信息),可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩
2.對(duì)以上場(chǎng)景的實(shí)踐
2.1 實(shí)踐場(chǎng)景1
運(yùn)行結(jié)果
因?yàn)橹袊?guó)位于東八區(qū),所以時(shí)間從1970年1月1日的8點(diǎn)開(kāi)始計(jì)算的
運(yùn)行結(jié)果
多個(gè)線程打印自己的時(shí)間(如果線程超級(jí)多就會(huì)產(chǎn)生性能問(wèn)題),所以要使用線程池。
運(yùn)行結(jié)果
但是使用線程池時(shí)就會(huì)發(fā)現(xiàn)每個(gè)線程都有一個(gè)自己的SimpleDateFormat對(duì)象,沒(méi)有必要,所以將SimpleDateFormat聲明為靜態(tài),保證只有一個(gè)
運(yùn)行結(jié)果
出現(xiàn)了秒數(shù)相同的打印結(jié)果,這顯然是不正確的。
出現(xiàn)問(wèn)題的原因
多個(gè)線程的task指向了同一個(gè)SimpleDateFormat對(duì)象,SimpleDateFormat是非線程安全的。
解決問(wèn)題的方案
方案1:加鎖
格式化代碼是在最后一句return dateFormat.format(date);,所以可以為最后一句代碼添加synchronized鎖
運(yùn)行結(jié)果
運(yùn)行結(jié)果中沒(méi)有發(fā)現(xiàn)相同的時(shí)間,達(dá)到了線程安全的目的
缺點(diǎn):因?yàn)樘砑恿藄ynchronized,所以會(huì)保證同一時(shí)間只有一條線程可以執(zhí)行,這在高并發(fā)場(chǎng)景下肯定不是一個(gè)好的選擇,所以看看其他方案吧。
方案2:使用ThreadLocal
運(yùn)行結(jié)果
使用了ThreadLocal后不同的線程不會(huì)有共享的 SimpleDateFormat 對(duì)象,所以也就不會(huì)有線程安全問(wèn)題
2.2 實(shí)踐場(chǎng)景2
當(dāng)前用戶信息需要被線程內(nèi)的所有方法共享
方案1:傳遞參數(shù)
可以將user作為參數(shù)在每個(gè)方法中進(jìn)行傳遞,
缺點(diǎn):但是這樣做會(huì)產(chǎn)生代碼冗余問(wèn)題,并且可維護(hù)性差。
方案2:使用Map
對(duì)此進(jìn)行改進(jìn)的方案是使用一個(gè)Map,在第一個(gè)方法中存儲(chǔ)信息,后續(xù)需要使用直接get()即可,
缺點(diǎn):如果在單線程環(huán)境下可以保證安全,但是在多線程環(huán)境下是不可以的。如果使用加鎖和ConcurrentHashMap都會(huì)產(chǎn)生性能問(wèn)題。
方案3:使用ThreadLocal,實(shí)現(xiàn)不同方法間的資源共享
使用 ThreadLocal 可以避免加鎖產(chǎn)生的性能問(wèn)題,也可以避免層層傳遞參數(shù)來(lái)實(shí)現(xiàn)業(yè)務(wù)需求,就可以實(shí)現(xiàn)不同線程中存儲(chǔ)不同信息的要求。
運(yùn)行結(jié)果
3.對(duì)ThreadLocal的總結(jié)
- 讓某個(gè)需要用到的對(duì)象實(shí)現(xiàn)線程之間的隔離(每個(gè)線程都有自己獨(dú)立的對(duì)象)
- 可以在任何方法中輕松的獲取到該對(duì)象
- 根據(jù)共享對(duì)象生成的時(shí)機(jī)選擇使用initialValue方法還是set方法
- 對(duì)象初始化的時(shí)機(jī)由我們控制的時(shí)候使用initialValue 方式
- 如果對(duì)象生成的時(shí)機(jī)不由我們控制的時(shí)候使用 set 方式
4.使用ThreadLocal的好處
- 達(dá)到線程安全的目的
- 不需要加鎖,執(zhí)行效率高
- 更加節(jié)省內(nèi)存,節(jié)省開(kāi)銷
- 免去傳參的繁瑣,降低代碼耦合度
5.ThreadLocal原理
- Thread
- ThreadLocal
- ThreadLocalMap
在Thread類內(nèi)部有有ThreadLocal.ThreadLocalMap threadLocals = null;這個(gè)變量,它用于存儲(chǔ)ThreadLocal,因?yàn)樵谕粋€(gè)線程當(dāng)中可以有多個(gè)ThreadLocal,并且多次調(diào)用get()所以需要在內(nèi)部維護(hù)一個(gè)ThreadLocalMap用來(lái)存儲(chǔ)多個(gè)ThreadLocal
5.1 ThreadLocal相關(guān)方法
T initialValue()
該方法用于設(shè)置初始值,并且在調(diào)用get()方法時(shí)才會(huì)被觸發(fā),所以是懶加載。
但是如果在get()之前進(jìn)行了set()操作,這樣就不會(huì)調(diào)用initialValue()。
通常每個(gè)線程只能調(diào)用一次本方法,但是調(diào)用了remove()后就能再次調(diào)用
void set(T t)
為這個(gè)線程設(shè)置一個(gè)新值
T get()
獲取線程對(duì)應(yīng)的value
void remove()
刪除對(duì)應(yīng)這個(gè)線程的值
6.ThreadLocal注意點(diǎn)
6.1 內(nèi)存泄漏
內(nèi)存泄露;某個(gè)對(duì)象不會(huì)再被使用,但是該對(duì)象的內(nèi)存卻無(wú)法被收回
強(qiáng)引用:當(dāng)內(nèi)存不足時(shí)觸發(fā)GC,寧愿拋出OOM也不會(huì)回收強(qiáng)引用的內(nèi)存
弱引用:觸發(fā)GC后便會(huì)回收弱引用的內(nèi)存
正常情況
當(dāng)Thread運(yùn)行結(jié)束后,ThreadLocal中的value會(huì)被回收,因?yàn)闆](méi)有任何強(qiáng)引用了
非正常情況
當(dāng)Thread一直在運(yùn)行始終不結(jié)束,強(qiáng)引用就不會(huì)被回收,存在以下調(diào)用鏈 Thread-->ThreadLocalMap-->Entry(key為null)-->value因?yàn)檎{(diào)用鏈中的 value 和 Thread 存在強(qiáng)引用,所以value無(wú)法被回收,就有可能出現(xiàn)OOM。
JDK的設(shè)計(jì)已經(jīng)考慮到了這個(gè)問(wèn)題,所以在set()、remove()、resize()方法中會(huì)掃描到key為null的Entry,并且把對(duì)應(yīng)的value設(shè)置為null,這樣value對(duì)象就可以被回收。
但是只有在調(diào)用set()、remove()、resize()這些方法時(shí)才會(huì)進(jìn)行這些操作,如果沒(méi)有調(diào)用這些方法并且線程不停止,那么調(diào)用鏈就會(huì)一直存在,所以可能會(huì)發(fā)生內(nèi)存泄漏。
6.2 如何避免內(nèi)存泄漏(阿里規(guī)約)
調(diào)用remove()方法,就會(huì)刪除對(duì)應(yīng)的Entry對(duì)象,可以避免內(nèi)存泄漏,所以使用完ThreadLocal后,要調(diào)用remove()方法。
6.3 ThreadLocal的空指針異常問(wèn)題
6.4 空指針異常問(wèn)題的解決
如果get方法返回值為基本類型,則會(huì)報(bào)空指針異常,如果是包裝類型就不會(huì)出錯(cuò)。這是因?yàn)榛绢愋秃桶b類型存在裝箱和拆箱的關(guān)系,造成空指針問(wèn)題的原因在于使用者。
6.5 共享對(duì)象問(wèn)題
如果在每個(gè)線程中ThreadLocal.set()進(jìn)去的東西本來(lái)就是多個(gè)線程共享的同一對(duì)象,比如static對(duì)象,那么多個(gè)線程調(diào)用ThreadLocal.get()獲取的內(nèi)容還是同一個(gè)對(duì)象,還是會(huì)發(fā)生線程安全問(wèn)題。
6.6 可以不使用ThreadLocal就不要強(qiáng)行使用
如果在任務(wù)數(shù)很少的時(shí)候,在局部方法中創(chuàng)建對(duì)象就可以解決問(wèn)題,這樣就不需要使用ThreadLocal。
6.7 優(yōu)先使用框架的支持,而不是自己創(chuàng)造
例如在Spring框架中,如果可以使用RequestContextHolder,那么就不需要自己維護(hù)ThreadLocal,因?yàn)樽约嚎赡軙?huì)忘記調(diào)用remove()方法等,造成內(nèi)存泄漏。
本文僅為自己學(xué)習(xí)時(shí)記下的筆記,參考自慕課:
https://coding.imooc.com/class/409.html
超強(qiáng)干貨來(lái)襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的c++ thread 内存泄漏_使用 ThreadLocal如何避免内存泄露?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: aoe网最早开始时间和最迟开始时间_关键
- 下一篇: mysql获取后一天_MySQL根据某一
