ThreadLocal实现原理与源码分析

ThreadLocal底层实现内部类:ThreadLocalMap

 

一、ThreadLocal的set方法源码分析:

(1)

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

 

1、Thread t = Thread.currentThread();

获取当前线程对象t

2、ThreadLocalMap map = getMap(t);

获取ThreadLocalMap对象,调用getMap方法,参数:Thread t,将当前对象传入到getMap方法,getMap的源码如(2)所示:

(2)

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

通过代码分析得知:map的值是当前对象t中定义的ThreadLocalMap对象【也就是说map是属于当前线程t中的属性】。

3、如果map!=null,则执行map.set(this,value)。ThreadLocalMap中的key为ThreadLocal<?>对象。this将当前的ThreadLocal对象作为key。

4、如果map为null,则创建map,调用createMap(t,value),createMap源码如(3)所示:

(3)

void createMap(Thread t, T firstValue) {

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

通过源码得知,初始化当前线程对象t的threadLocals实例。并且将value保存到ThreadLocalMap中,将当前的ThreadLocal对象作为key。

 

注意:ThreadLocalMap实例是当前线程对象的属性,并且ThreadLocalMap中的key是当前ThreadLocal对象。

 

二、ThreadLocal的get方法源码分析:

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

1、Thread t = Thread.currentThread();获取当前线程对象t。

2、ThreadLocalMap map = getMap(t);通过以上源码得知,返回当前线程对象t的threadLocals实例。

3、如果map!=null,执行:ThreadLocalMap.Entry e = map.getEntry(this);源码参考(2-1):

(2-1)

private Entry getEntry(ThreadLocal<?> key) {

int i = key.threadLocalHashCode & (table.length - 1);

Entry e = table[i];

if (e != null && e.get() == key)

return e;

else

return getEntryAfterMiss(key, i, e);

}

3.1、e.get() 调用的是WeakReference中的get方法,返回的是ThreadLocal对象,即ThreadLocalMap的key值。

如果e.get()==key,则返回entry对象。否则,执行getEntryAfterMiss(key,i,e),源码如(2-2)

(2-2)

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {

Entry[] tab = table;

int len = tab.length;

while (e != null) {

ThreadLocal<?> k = e.get();

if (k == key)

return e;

if (k == null)

expungeStaleEntry(i);

else

i = nextIndex(i, len);

e = tab[i];

}

return null;

}

3.2、如果e!=null,则执行e.get()获取ThreadLocal对象k,

如果k==key,则返回e;

如果k==null,则执行expungeStaleEntry(i);//删除过期的entry对象,【如果key为null,则entry对象为过期对象】

如果k!=null && k!=key,执行i=nextIndex(i,len)则获取tab[ ]数组的下一个索引,

e=tab[i]继续从tab[ ]数组中获取entry对象e。执行3.2操作。

3.3、如果e!=null,则直接获取e.value返回。如果e==null,则执行:setInitialValue();

4、如果 map==null,则执行:setInitialValue();源码(2-3)如下:

(2-3)

private T setInitialValue() {

T value = initialValue();

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

return value;

}

(2-4)

protected T initialValue() {

return null;

}

4.1、初始化value,执行initialValue()方法,返回null。源码(2-4)

4.2、获取当前线程对象t

4.3、调用getMap(t),获取ThreadLocalMap对象。

4.4、如果map!=null,则执行map.set(this,value);否则,执行createMap(t,value),创建ThreadLocalMap,并且设置key与value。返回value。【value值为null】

 

三、原理

线程共享变量缓存如下:

Thread.ThreadLocalMap<ThreadLocal, Object>;

1、Thread: 当前线程,可以通过Thread.currentThread()获取。

2、ThreadLocal:我们的static ThreadLocal变量。

3、Object: 当前线程共享变量。

我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocal, Object>,然后根据当前ThreadLocal获取当前线程共享变量Object。

ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。

 

这种存储结构的好处:

1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。

2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。

 

关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:

当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。

虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。

1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;

2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值