ThreadLocal
定義
ThreaLocal提供了線程本地的實例。它與普通變量的區(qū)別在于倔撞,每個使用該變量的線程都會初始化一個==完全獨立==的實例副本
synchronize解決線程數(shù)據(jù)共享的帚屉,ThreadLocal提供了線程隔離
適用場景
適用于每個線程需要自己獨立的實例谜诫,且該實例需要在多個方法中被使用,也就是變量在線程間隔離而在方法或類間共享的場景
- 例如攻旦,JAVA web中喻旷,我們需要用session獲取信息,有時候又需要修改session信息牢屋。
- 這時候也可以用局部變量來解決問題且预,但是不好的地方是什么呢?
//在每個線程內(nèi)都構(gòu)建session實例
public class TheadLocalTest {
class SessionLocal{
String status;
}
public SessionLocal createSession(){
return new SessionLocal();
}
//需要顯式傳遞SessionLocal實例
public String get(SessionLocal sessionLocal){
return sessionLocal.status;
}
//需要顯式傳遞SessionLocal實例
void setStatus(SessionLocal sessionLocal,String status){
sessionLocal.status=status;
}
public static void main(String[] args) {
new Thread(() -> {
TheadLocalTest theadLocalTest = new TheadLocalTest();
SessionLocal session = theadLocalTest.createSession();
theadLocalTest.get(session);
System.out.println(session.status);
theadLocalTest.setStatus(session, "close");
theadLocalTest.get(session);
System.out.println(session.status);
}).start();
}
}
也可以實現(xiàn)需求烙无,但是需要顯式傳遞session對象锋谐,方法間耦合度較高
使用ThreadLocal實現(xiàn)
public class TheadLocalTest {
//創(chuàng)建一個ThreadLocal對象
public static ThreadLocal<SessionLocal> session = new ThreadLocal<SessionLocal>();
class SessionLocal {
String status;
}
//創(chuàng)建一個SessionLocal
public void createSession() {
session.set(new SessionLocal());
}
//直接通過ThreadLocal獲取,不用顯式傳遞SessionLocal實例
public String get() {
SessionLocal sessionLocal = session.get();
return sessionLocal.status;
}
//直接通過ThreadLocal設(shè)置皱炉,不用顯式傳遞SessionLocal實例
void setStatus(String status) {
session.get().status = status;
}
public static void main(String[] args) {
new Thread(() -> {
TheadLocalTest theadLocalTest = new TheadLocalTest();
theadLocalTest.createSession();
theadLocalTest.get();
System.out.println(theadLocalTest.get());
theadLocalTest.setStatus("close");
theadLocalTest.get();
System.out.println(theadLocalTest.get());
}).start();
}
}
實現(xiàn)原理
public class ThreadLocal<T> {
……
//由靜態(tài)內(nèi)部類ThreadLocalMap提供Map
static class ThreadLocalMap {
//每個Entry都是一個對鍵的弱引用
static class Entry
extends WeakReference<ThreadLocal<?>> {
Object value;
//每個Entry都包含了一個對值的強引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
1. 使用弱引用的原因:當沒有強引用指向ThreadLocal變量時怀估,它可以被回收。從而導致ThreadLocal不能被回收而造成內(nèi)存泄露合搅。
2. 另外一種內(nèi)存泄露:ThreadLocalMap維護ThreadLocal變量和具體映射多搀,當ThreadLocal被回收,映射的鍵就變?yōu)閚ull灾部。ThreadLocalMap中的Entry無法被移除康铭。從而使得實例被該Entry引用而無法被回收。
原理圖
從上面代碼也可以看出
Thread赌髓、ThreadLocal从藤、ThreadLocalMap、Entry之間的關(guān)系:
image
- 一個Thread只有一個ThreadLocalMap
- 一個ThreadLocalMap可以用多個ThreadLocal對象
- 一個ThreadLocal對象對應一個Entry
內(nèi)存泄露
image
- 實線代表強引用锁蠕,虛線代表弱引用
- 虛線表示ThreadLocalMap是使用ThreadLocal的弱引用作為key的夷野。弱引用的對想在GC時會被回收
- 如果一個ThreadLocal沒有外部強引用(圖中的ref)來引用它,系統(tǒng)GC的時候荣倾,ThreadLocal就會被回收悯搔,就會出現(xiàn)很多key為null的Entry,如果線程不結(jié)束,value就會一直被引用舌仍,從而Entry就不能被回收妒貌,造成內(nèi)存泄漏
是弱引用導致的內(nèi)存泄露嗎?
- 如果key使用強引用
- 引用的ThreadLocal對象(ref)被回收了铸豁,但是ThreadLocalMap還持有ThreadLocal的強引用灌曙,那么ThreadLocal不會被回收,從而導致Entry內(nèi)存泄露
- 弱引用
- 不會出現(xiàn)上面的問題节芥。ThreadLocal會被回收在刺,value在下一次調(diào)用set、get、remove的時候也會清除蚣驼。
- 所以忍燥,相比強引用,弱引用多一層保障
優(yōu)化
- 在ThreadLocal的get(),set(),remover()的時候 都會清除線程ThreadLocalMap里所有key為null的value
- 但是這些并不能保證不會內(nèi)存泄露
- 使用static的ThreadLocal,延長了生命周期
- 分配使用了ThreadLocal,但是沒有調(diào)用get隙姿、set、remover方法
- 如何避免厂捞?
- 使用完ThreadLocal,都調(diào)用remove()输玷,清除數(shù)據(jù)。
- 如果ThreadLocal的生命周期需要和項目周期一樣靡馁,就不能用remove了欲鹏。