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(ThreadLocal>firstKey,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(ThreadLocal>k,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(ThreadLocal>k,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(ThreadLocal>key){
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!
|
|