ThreadLocal的使用及內(nèi)存泄漏問題分析
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
ThreadLocal原理概述
每一個Thread對象均含有一個ThreadLocalMap類型的成員變量threadLocals,它存儲當(dāng)前線程中所有ThreadLocal對象及其對應(yīng)的值。部分源碼如下
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
而ThreadLocalMap中的核心就是一個個Entry對象,以下是ThreadLocalMap的部分源碼
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
我們通過一張圖來清楚地表示ThreadLocal的引用關(guān)系

ThreadLocal簡單應(yīng)用
業(yè)務(wù)需求:有10位顧客去餐廳分別點了1~10十款套餐,要求每個顧客都能準(zhǔn)確拿到自己點的套餐
代碼示例(不使用ThreadLocal的情況)
public class Demo {
//餐廳菜單
private String menu;
private String getMenu() {
return menu;
}
private void setMenu(String menu) {
this.menu = menu;
}
public static void main(String[] args) {
final Demo demo = new Demo();
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//顧客點餐
demo.setMenu(Thread.currentThread().getName() + "的套餐");
System.out.println("----------------------");
//顧客取餐
System.out.println(Thread.currentThread().getName() + "拿走了" + "["+demo.getMenu()+"]");
}
});
thread.setName("顧客" + i);
thread.start();
}
}
}
運行結(jié)果(嘗試多運行幾次,排除程序運行結(jié)果出現(xiàn)的偶然性)
D:\installPath\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\installPath\IntelliJ IDEA 2019.1.4\lib\idea_rt.jar=56135:D:\installPath\IntelliJ IDEA 2019.1.4\bin" -Dfile.encoding=UTF-8 -classpath D:\installPath\Java\jdk1.8.0_121\jre\lib\charsets.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\deploy.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\access-bridge-64.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\cldrdata.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\dnsns.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\jaccess.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\jfxrt.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\localedata.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\nashorn.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunec.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunjce_provider.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunmscapi.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunpkcs11.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\zipfs.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\javaws.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jce.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jfr.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jfxswt.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jsse.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\management-agent.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\plugin.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\resources.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\rt.jar;D:\Users\13209\IdeaProjects\spring-study\target\classes com.kang.controller.Demo
----------------------
----------------------
----------------------
顧客6拿走了[顧客5的套餐]
----------------------
----------------------
----------------------
顧客9拿走了[顧客9的套餐]
顧客3拿走了[顧客7的套餐]
----------------------
----------------------
顧客8拿走了[顧客10的套餐]
顧客4拿走了[顧客6的套餐]
----------------------
顧客1拿走了[顧客6的套餐]
顧客5拿走了[顧客10的套餐]
顧客7拿走了[顧客10的套餐]
----------------------
顧客2拿走了[顧客9的套餐]
顧客10拿走了[顧客10的套餐]
Process finished with exit code 0
我們開啟了10個線程分別表示10個顧客,由于線程之間相互沒有隔離,導(dǎo)致很多套餐被拿錯,甚至有些套餐沒人拿,這顯然不符合我們的業(yè)務(wù)需求。那么我們在程序中引入ThreadLocal對象來看一下運行結(jié)果
代碼示例(引入ThreadLocal對象)
public class Demo {
//引入ThreadLocal對象
ThreadLocal<String> threadLocal=new ThreadLocal();
//餐廳菜單
private String menu;
private String getMenu() {
//return menu;
return threadLocal.get();
}
private void setMenu(String menu) {
//this.menu = menu;
//通過ThreadLocal對象將變量menu綁定到當(dāng)前線程
threadLocal.set(menu);
}
public static void main(String[] args) {
final Demo demo = new Demo();
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//顧客點餐
demo.setMenu(Thread.currentThread().getName() + "的套餐");
System.out.println("----------------------");
//顧客取餐
System.out.println(Thread.currentThread().getName() + "拿走了" + "["+demo.getMenu()+"]");
}
});
thread.setName("顧客" + i);
thread.start();
}
}
}
運行結(jié)果(嘗試多運行幾次,排除程序運行結(jié)果出現(xiàn)的偶然性)
D:\installPath\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\installPath\IntelliJ IDEA 2019.1.4\lib\idea_rt.jar=56301:D:\installPath\IntelliJ IDEA 2019.1.4\bin" -Dfile.encoding=UTF-8 -classpath D:\installPath\Java\jdk1.8.0_121\jre\lib\charsets.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\deploy.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\access-bridge-64.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\cldrdata.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\dnsns.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\jaccess.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\jfxrt.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\localedata.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\nashorn.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunec.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunjce_provider.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunmscapi.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\sunpkcs11.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\ext\zipfs.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\javaws.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jce.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jfr.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jfxswt.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\jsse.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\management-agent.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\plugin.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\resources.jar;D:\installPath\Java\jdk1.8.0_121\jre\lib\rt.jar;D:\Users\13209\IdeaProjects\spring-study\target\classes com.kang.controller.Demo
----------------------
----------------------
----------------------
顧客2拿走了[顧客2的套餐]
顧客3拿走了[顧客3的套餐]
----------------------
----------------------
顧客1拿走了[顧客1的套餐]
----------------------
----------------------
----------------------
顧客5拿走了[顧客5的套餐]
----------------------
顧客7拿走了[顧客7的套餐]
----------------------
顧客6拿走了[顧客6的套餐]
顧客8拿走了[顧客8的套餐]
顧客9拿走了[顧客9的套餐]
顧客10拿走了[顧客10的套餐]
顧客4拿走了[顧客4的套餐]
Process finished with exit code 0
這樣的設(shè)計便實現(xiàn)了業(yè)務(wù)需求,由此我們可以看出ThreadLocal類的作用是:提供線程內(nèi)的局部變量,不同線程之間不會相互干擾,這種變量僅在線程的生命周期內(nèi)起作用。
ThreadLocal的內(nèi)存泄漏問題
首先我們先了解一下內(nèi)存泄漏和內(nèi)存溢出的概念
內(nèi)存泄漏(Memory Leak):程序中已動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費,導(dǎo)致程序運行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。我們都知道內(nèi)存泄漏的堆積終將導(dǎo)致嚴(yán)重的內(nèi)存溢出。
內(nèi)存溢出(Out Of Memory):無法給申明的對象提供足夠的內(nèi)存空間,程序無法再正常運行,也就是我們常說的OOM。
說到這,我們順便來回顧一下Java對象的四大引用
強(qiáng)引用(Strong Reference):最普遍使用的引用,垃圾回收器不會回收一個持有強(qiáng)引用的對象。即使內(nèi)存空間不足時,Java虛擬機(jī)寧愿拋出Out Of Memory Error 錯誤,終止程序運行,也不會依靠回收具有強(qiáng)引用的對象來解決內(nèi)存空間不足的問題。
軟引用(Soft Reference):一個只持有軟引用的對象,只要內(nèi)存空間充足,垃圾回收器就不會回收它,一旦內(nèi)存空間不足,就會回收這些對象的內(nèi)存來保證程序的運行。
弱引用(Weak Reference):只持有弱引用的對象比只持有軟引用的對象擁有更加短暫的生命周期。當(dāng)垃圾回收器線程掃描到只具有弱引用的對象,不管當(dāng)前內(nèi)存空間是否充足,都會回收這些對象的內(nèi)存。不過,垃圾回收器線程是一個優(yōu)先級很低的線程,所以不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。
虛引用(Phantom Reference):就是字面意思,與其他引用都不同的是虛引用不會決定對象的生命周期,也就是說只持有虛引用的對象就和沒有任何引用一樣,任何時候都可能被垃圾回收器線程回收,所以虛引用必須和引用隊列(Reference Queue)聯(lián)合使用來發(fā)揮自己的作用。
內(nèi)存泄漏的根本原因
所有Entry對象都被ThreadLocalMap類的實例化對象threadLocals持有,當(dāng)ThreadLocal對象不再使用時,ThreadLocal對象在棧中的引用就會被回收,一旦沒有任何引用指向ThreadLocal對象,Entry只持有弱引用的key就會自動在下一次YGC時被回收,而此時持有強(qiáng)引用的Entry對象并不會被回收。
簡而言之:threadLocals對象中的entry對象不在使用后,沒有及時remove該entry對象 ,然而程序自身也無法通過垃圾回收機(jī)制自動清除,從而導(dǎo)致內(nèi)存泄漏。
解決方案:只要在使用完ThreadLocal對象后,調(diào)用其remove方法刪除對應(yīng)的Entry,即可從根本解決問題。
————————————————
版權(quán)聲明:本文為CSDN博主「才哈哈」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/weixin_45333509/article/details/115756410
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼 2 秒
感謝點贊支持下哈 
