配色: 字号:
ThreadLocal 源码解读
2016-10-24 | 阅:  转:  |  分享 
  
ThreadLocal源码解读

基本概念

当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭ThreadConfinement。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。



线程封闭其中一种实现方式就是ThreadLocal的方式,简单说,就是对于一个共享变量为每个线程都保存了一个副本,ThreadLocal为每个使用该变量的线程提供独立的变量副本,使之成为独立的线程局部变量。



ThreadLocal模式解决的是同一线程中隶属于不同开发层次的数据共享问题,而不是在不同的开发层次中进行数据传递。使用ThreadLocal模式,可以使得数据在不同的编程层次得到有效地共享。







从上面图中我们可以看到,由于ThreadLocal所操作的是维持于整个Thread生命周期的副本(ThreadLocalMap),所以无论在J2EE程序程序的哪个层次(表示层、业务逻辑层或者持久层),只要在一个Thread的生命周期之内,存储于ThreadLocalMap中的对象都是线程安全的(因为ThreadLocalMap本身仅仅隶属于当前的执行线程,是执行线程内部的一个属性变量。我们用图中的阴影部分来表示这个变量的存储空间)。而这一点,正是被我们用于来解决多线程环境中的变量共享问题的核心技术。ThreadLocal的这一特性也使其能够被广泛地应用于J2EE开发中的许多业务场景。

摘自:http://wely.iteye.com/blog/2295284



ThreadLocal使用是比较直观的,本次的重点也不在于使用,而在内部的实现方式。



实现源码

Thread类内部维护了ThreadLocalMap结构,但是ThreadLocalMap的维护交给了ThreadLocal对象



publicclassThread{

//由于使用了内部类,Thread对象有threadLocals引用,但是操作权在ThreadLocal对象手中

ThreadLocal.ThreadLocalMapthreadLocals=null;

}

//ThreadLocalMap是定义在Thread中,内部维护了一个Entry数组

/

ThreadLocalMapisacustomizedhashmapsuitableonlyfor

maintainingthreadlocalvalues.Nooperationsareexported

outsideoftheThreadLocalclass.Theclassispackageprivateto

allowdeclarationoffieldsinclassThread.Tohelpdealwith

verylargeandlong-livedusages,thehashtableentriesuse

WeakReferencesforkeys.However,sincereferencequeuesarenot

used,staleentriesareguaranteedtoberemovedonlywhen

thetablestartsrunningoutofspace.

/

staticclassThreadLocalMap{

//每个ThreadLocal对象都维护了一个threadLocalHashCode,根据这个值和ThreadLocalMap的容量进行交运算来确定下标

privateEntry[]table;



ThreadLocalMap(ThreadLocalfirstKey,ObjectfirstValue){

table=newEntry[INITIAL_CAPACITY];

inti=firstKey.threadLocalHashCode&(INITIAL_CAPACITY-1);

table[i]=newEntry(firstKey,firstValue);

size=1;

setThreshold(INITIAL_CAPACITY);

}

}

//实际上Entry是对ThrealLocal的封装

/

TheentriesinthishashmapextendWeakReference,using

itsmainreffieldasthekey(whichisalwaysa

ThreadLocalobject).Notethatnullkeys(i.e.entry.get()

==null)meanthatthekeyisnolongerreferenced,sothe

entrycanbeexpungedfromtable.Suchentriesarereferredto

as"staleentries"inthecodethatfollows.

/

staticclassEntryextendsWeakReference>{

/ThevalueassociatedwiththisThreadLocal./

Objectvalue;



Entry(ThreadLocalk,Objectv){

super(k);

value=v;

}

}





内存相关

Entry是对ThreadLocal的简单封装,继承了WeakReference,使用WeakReference的好处是,reference实例不会影响到被应用对象的GC回收行为(即只要对象被WeakReference对象之外所有的对象解除引用后,该对象可以被GC回收)只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。



以下面的代码为例来说明内存的相关情况:



publicclassThreadLocalDemo{

@Test

publicvoidtest()throwsInterruptedException{

Demodemo=newDemo();

ThreadLocalthreadLocal=newThreadLocal(){

@Override

protectedvoidfinalize()throwsThrowable{

super.finalize();

System.out.println("threadLocalfinalize");

}

};

threadLocal.set(demo);

demo=null;//1

//threadLocal.set(null);//2

//threadLocal.remove();//4

threadLocal=null;//3

System.gc();



}

classDemo{

bytedata[]=null;

publicDemo(){

data="abcd".getBytes();

}

@Override

protectedvoidfinalize()throwsThrowable{

System.out.println("demofinalize");

super.finalize();

}

}

}

示例代码运行后,只会输出threadLocalfinalize,和直觉不太一致,因为在程序1处设置了目标对象为null,期望结果是GC的时候回收掉这块内存,但是demofinalize并没有输出,表明它并没有被回收。简单画了一下内存图(不要在意细节,就是个示例)







可以看到,demo=null,而demo的示例对象仍然有Entry中的value的强引用,将代码稍作修改也可以看出这一点



@Test

publicvoidtest()throwsInterruptedException{

Demodemo=newDemo();

ThreadLocalthreadLocal=newThreadLocal(){

@Override

protectedvoidfinalize()throwsThrowable{

Super.finawww.baiyuewang.netlize();

System.out.println("threadLocalfinalize");

}

};

threadLocal.set(demo);

demo=null;

System.out.println(newString(threadLocal.get().data));



System.gc();

}

输出结果为abcd,也印证了上面的观点。事实上我们希望回收了这块内存,但是显然没有达到预期,这可能会导致内存泄露。



WeakReference的使用

回过头来再看第一段代码,如果按照分析demo=null的方式来说明,那么在代码中设置了threadlocal=null,而Entry的key同样指向了这块内存,照理说也应该不会被回收,但是第一段代码输出了结果threadLocalfinalize,说明ThreadLocal对象被回收了。这个的原因就是WeakReference的使用



staticclassEntryextendsWeakReference>{

/ThevalueassociatedwiththisThreadLocal./

Objectvalue;



Entry(ThreadLocalk,Objectv){

super(k);

value=v;

}

}

由于设置了Entry对象中ThreadLocal是WeakReference的,上面也提到过不会影响到对象的GC操作,因此当设置threadlocal=null的时候,对应的内存可以被GC回收掉



内存泄露的解决方式

如果想要让GC回收demo实例对应的内存区域,那么应该怎么办呢?有两种方式,放开第一段代码注释掉的2或者4处就可以了。分析一下原因:



//threadLocal.set(null);

publicvoidset(Tvalue){

Threadt=Thread.currentThread();

ThreadLocalMapmap=getMap(t);

if(map!=null)

map.set(this,value);

else

createMap(t,value);

}

结合内存图来看,直接断开了Entry的value的指向,所以在下次gc的时候,demo内存没有了强引用,所以直接被GC了,也就执行了finalize()方法



//threadLocal.remove();

publicvoidremove(){

ThreadLocalMapm=getMap(Thread.currentThread());

if(m!=null)

m.remove(this);

}

privatevoidremove(ThreadLocalkey){

Entry[]tab=table;

intlen=tab.length;

inti=key.threadLocalHashCode&(len-1);

for(Entrye=tab[i];

e!=null;

e=tab[i=nextIndex(i,len)]){

if(e.get()==key){

e.clear();

expungeStaleEntry(i);

return;

}

}

}

remove方法更彻底,直接把Entry对象设置为null,下次GC自然会回收相应内存



总结一下

ThemajorissuewithThreadLocalisthepotentialriskofmemoryleak.Indeed,theThreadLocalMapcontainsanarrayofEntrywhosekeyvalueextendsWeakReferencesoonecannaivelythinkthatitwillbegarbagedbyGCautomaticallywhenmemoryislow.Butthefactisthatonlythekey(ThreadLocalobject)isaweakreference,notthetargetobjectitself!

献花(0)
+1
(本文系thedust79首藏)