什么是ThreadLocal?实现原理?

基于JDK1.8
1.ThreadLocal有什么用?
2.ThreadLocal为什么能实现每个线程能有一个独立的变量副本
3.每个线程的变量副本存储位置在哪儿
4.变量副本是如何从共享变量中复制出来的

1.ThreadLocal有什么用

ThreadLocal是为了各个线程都有一份自己独立的变量(对象),而不是用来解决共享对象的多线程访问问题的。

2.ThreadLocal原理

//Thread类源码
public class Thread implements RunnableP{
	//与此线程有关的ThreadLocal值,由ThreadLocal类维护
	ThreadLocal.ThreadLocalMap threadLocals = null;
	//与此线程有关的InheritableThreead值。由ThreadLocal类维护
	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

从上面Thread类源码可以看出Thread类有一个threadLocals和一个inheritableThreadLocals变量,它们都是ThreadLocalMap类型的变量,我们可以把ThreadLocalMap理解为ThreadLocal类实现的定制化的HashMap。默认情况下这两个变量都是null,我们调用ThreadLocalMap类对应的get()、set()方法。

public void set(T value){
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if(map!=null){
		map.set(this,value);
	}else{
		creatMap(t,value);
	}
}
ThreadLocalMap getMap(Thread t){
	return t.threadLocals;
}
void creadMap(Thread t,T firstValue){
	t.threadLocals = new ThreadLocalMap(this,firstValue);
}

通过上面这些内容,我们可以推测得出结论。最终的变量是放在了当前线程的ThreadLocalMap中,并不是存在ThreadLocal上,ThreadLocal可以理解为只是ThreadLocalMap的封装,传递了变量值。ThreadLocal类可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

public T get(){
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if(map!=null){
		ThreadLocalMap.Entry e = map.getEntry(this);
		if(e!=null){
			@SuppresWarnings("unchecked");
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

ThreadLocal内部维护的一个类似Map的ThreadLocalMap数据结构,key为当前的ThreadLocal对象,值为Object对象。

ThreadLocalMap(ThreadLocal<?> firstKey,Object firstValue){}

如果我们在同一个线程中声明了两个ThreadLocal对象的话,会使用Thread内部都是使用仅有的ThreadLocalMap存放数据段,ThreadLocalMap的key就是ThreadLocal对象,value就是ThreadLocal对象调用set方法设置的值。

在这里插入图片描述

ThreadLocalMap是ThreadLocal的静态内部类
在这里插入图片描述
问:ThreadLocal为什么能实现每个线程能有一个独立的变量副本
答:因为在每个线程下面有个ThreadLocals,当需要进行线程隔离变量时候,那么就会调用他的createMap(Thread t,T value);

void creatMap(Thread t,T value){
	t.threadLoclas = new ThreadLocalMap(this,value);
}

该数据结构是位于每个线程下面的,所以可以实现独立变量副本。
问:每个线程的变量副本存储位置在哪儿
答:同上。该变量副本是以ThreadLocalMap的数据结构以ThreadLocal对象为值,Object对象为value存放在每个线程下面的threadLocals中的。
问:变量副本是如何从共享变量中复制出来的
其实就是问如何从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){
			@SuppresWarnings("unchecked");
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

可以看出先获取到了当前变量中的ThreadLocalMap数据结构。
如果ThreadLocal为空,会对ThreadLocal进行初始化。
如何从ThreadLocalMap中以当前ThreadLocal变量为值,获取相应的value,也就是获取到线程隔离的变量了。即将变量副本从共享变量中复制出来。

3.ThreadLocal内存泄漏了解吗

ThreaLocalMap中使用的key为ThreadLocal的弱引用,而value是强引用。所以如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocal中就会出现key为null的Entry。假如我们不采取任何措施的话,value可能拥有不会被GC回收,这个时候就可能产生内存泄漏。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录。使用完ThreadLocal方法后,最后手动调用remove()方法。

static class Entry entends WeakReference<ThreadLocal<?>>{
	Object value;
	Entry(ThreadLocal<?> k,Object v){
		super(k);
		value = v;
	}
}

注意:弱引用与软引用的区别。
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象。不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些具有弱引用的对象。
弱引用可以和一个引用队列联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4.ThreadLocalMap中为什么存放的key为弱引用

因为ThreadLocalMap中存放的是变量的副本。
当原来对象(变量)失效的时候,就可以触发垃圾回收机制。
至于为什么value设置成强引用,那是因为如果value设置成弱引用,在key还未清理的时候,可能会导致获取到null。
确实ThreadLocalMap有内存泄漏的风险。但是ThreadLocal内部的set()和get()方法执行的时候都会进行remove()key为null的引用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值