ThreadLocal详细讲解

public class ThreadLocalTest {
		private String content;

		public String getContent() {
			return content;
		}

		public void setContent(String content) {
			this.content = content;
		}
		public static void main(String[] args) {
			ThreadLocalTest tt=new ThreadLocalTest();
			for(int i=0;i<5;i++) {
				Thread thread=new Thread(new Runnable() {

					@Override
					public void run() {
						// TODO 自动生成的方法存根
						tt.setContent(Thread.currentThread().getName()+"的数据");
						System.out.println(Thread.currentThread().getName()+" ---> "+tt.getContent());
						System.out.println("---------------------------------");
					}
					
				});
				thread.setName("线程"+i);
				thread.start();
			}
		}
}

 从运行结果可以看出:线程0使用了线程1的数据

解决方法1:

@Override
					public void run() {
						synchronized(ThreadLocalTest.class) {
							// TODO 自动生成的方法存根
							tt.setContent(Thread.currentThread().getName()+"的数据");
							System.out.println(Thread.currentThread().getName()+" ---> "+tt.getContent());
							System.out.println("---------------------------------");
						}
					}

 

 

 缺点:效率变低了

解决方法2:

private ThreadLocal<String> tl=new ThreadLocal<>();
		public String getContent() {
//			return content;
			return tl.get();
		}

		public void setContent(String content) {
//			this.content = content;
			tl.set(content);
			
		}

 

 与synchronized相比:效率变高了,可以并发执行

ThreadLocal是用来干嘛的?

从下面三个方面解释:

①线程并发:ThreadLocal应用于多线程并发的场景中

②数据传递:同一线程中的不同组件之间可以数据传递

③数据隔离:别的线程想访问当前线程的数据是不行的,数据隔离,互不干扰

ThreadLocal的应用场景:

转账是一个最典型的案例:转账可能会出现一些异常导致事务没有回滚,账户的前减少了,而另一个账户的前没有增加。这里就需要开启事务,这里就需要Service层和Dao层前后使用的Connection对象要一致,这时就可以使用ThreadLocal将对象和线程进行一个绑定,前后获取的都是同一个Connection对象。当然,也可以使用值传递和synchronized关键字进行处理,但是效率降低了且代码之间的耦合度增高了。

ThreadLocal的底层设计:

 

①Thread中有一个Map属性

②由ThreadLocalMap进行管理,由ThreadLocal进行设置、获取值

③Map的key为ThreadLocal对象

④不同线程想要访问当前线程的副本是不行的,数据是隔离的,互不干扰

这样设计的好处:①Entry的数量变少了②Thread销毁时,ThreadLocalMap也会进行销毁,减少内存的消耗

ThreadLocal中的常用方法:

 1.initialValue():这是个延时方法,当未调用set方法之前先调用了get方法,使用此方法进行一个value的赋值

 2.set():为当前线程绑定变量值

 3.get():  获取当前线程绑定的变量值

 4.remove(): 移除当前线程绑定的变量值

set ()方法执行流程:

①获取当前线程的对象,根据当前线程对象获取map

②map不为空,为map设置值(key为当前ThreadLocal对象,value为传递进来的值)

③map为空,则创建一个新的map并赋值

get()方法的执行流程: 

①获取当前线程对象,获取当前线程对象的map

②map存在,以ThreadLocal对象为key获取ThreadLocalMap中的Entry对象e,e不为空则返回value

③map不存在、Entry不存在则调用initialValue方法并执行创建一个map并进行赋值

 

 remove()方法执行流程:

①获取当前线程对象,获取当前线程对象的map

②map存在,根据ThreadLocal对象为key进行移除

为了阐述下面要引出的问题,先将几个概念:

内存溢出:无法为申请者提供足够的内存

内存泄漏:程序已分配的堆内存由于程序无法释放或者不能释放,导致程序运行变慢严重系统崩溃,最终导致内存溢出

强引用:new出来的结构都是强引用,只要该对象还被引用的话,jvm就不会回收它

弱引用:jvm的垃圾回收器发现了它,不管内存是否足够,都会将其回收

这里插上一句:ThreadLocalMap中的Entry的key是弱引用

 

ThreadLocal内存泄漏的原因分析:

有些博客上说ThreadLocal内存泄漏的原因是因为ThreadLocalMap的Entry使用的是弱引用。我们可以用反证法进行一个证明,假设key使用强引用的话。

强引用分析:

① 当业务执行完成,将ThreadLocal对象的引用进行回收之后,由于key是强引用所以堆空间中的ThreadLocal对象无法回收

②只要没有调用remove方法手动删除Entry并且当前线程还没结束,就存在一条强引用链:currentThread ref ->currentThread --> ThreadLocalMap -->Entry,这样的话Entry就不会

被回收。由此可以看出,就算key使用强引用依旧可能会导致内存泄漏

弱引用分析:

 ①当业务执行完成后,ThreadLocal引用被回收,key为弱引用所以ThreadLocal对象会被回收,key会被置为null。

②如果没有手动调用remove方法移除Entry并且当前线程没有结束,始终存在一条强引用链:

currentThread ref --> currentThread --> ThreadLocalMap --> Entry --> value。key置为null所以value永远不会被访问到,会导致内存泄漏。

总结:无论key使用强引用还是弱引用都可能会导致内存泄漏,根本原因就是:ThreadLocalMap的生命周期和Tread的生命周期一致

解决ThreadLocal内存泄漏的方法:

①手动调用remove方法

②ThreadLocal使用完成后,让线程结束。显然,这种方式不好操作,因为我们使用的线程都是来自线程池中的,用完就回收了

有些同学可能会问既然强引用和弱引用都会导致内存泄漏,为什么使用弱引用?

这是因为弱引用比强引用多一层保障,使用弱引用,当ThreadLocal对象回收之后key置为null,ThreadLocalMap无论是set/getEntry都会将value置为null。就算我们忘记手动remove了,对应的value下次无论调用ThtreadLocalMap中的set、get、remove都会将其先清空。

ThreadLocalMap中的set底层是怎么实现的?

①以ThreadLocal对象为key,根据相应的算法得到数组下标i,获取i位置上的Entry

②Entry存在,Entry中的key与传入的key一致,则将新的value替换就的value

③Entry存在,key为null,调用方法将Entry进行替换

不断循环探测,直到Entry为null,之前都没return,则创建一个新的Entry并且插入,size加1

调用方法清理key为null的Entry,如果没有key为null的Entry并且size>=阈值(12)则调用rehash()

进行全表的扫描

 

ThreadLocalMap中的Entry存放如何避免hash 冲突的?

主要使用线性探测法,一次探测下一个地址,直到找到为null的地址,若整个空间都不满足则溢出

例如:数组的长度为16,根据相应的算法得出i=14,t[14]如果不满足,探测t[15],如果不满足则探测t[0],直至找到满足的位置。此时,数组可以看成一个环形数组

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值