【多线程编程】--ThreadLocal、InheritableThreadLocal(ITL)、TransmittableThreadLocal(TTL)解析

一、前言

最近项目中,有个同事开发中定义了一个全局变量,为了防止数据混乱【并发请求这个接口时不同线程操作,会影响变量值】,他加了一个锁,这就大大影响了性能。我默默推荐了ThreadLocal,说可以解决他的问题。最后他虽然用了ThreadLocal,但又遇到问题,说代码执行到后面发现值莫名被置空了【看了代码发现,为了不影响返回结果,异步执行了一部分业务】。。。。。
基于项目中使用到了ThreadLocal,同时一些复杂的场景,所以这篇文章将介绍ThreadLocal系列的原理,以便更好的使用。

使用ThreadLocal常见有以下的问题要思考:
(1)、主线程怎么传值给子线程
(2)、子线程修改了变量值,对主线程或其他线程是否有影响
(3)、ThreadLocal这么好用,是否无节制的使用,有什么需要注意的么?

因此,这篇文章将介绍ThreadLocal、InheritableThreadLocal(ITL)、TransmittableThreadLocal(TTL)的原理和优缺点。

二、ThreadLocal

ThreadLocal是避免多线程并发问题,避免竞争。主要是set()、get()、remove()几个方法。

2.1、为什么会用到ThreadLocal(ThreadLocal应用场景)

对时间的转化,我们经常使用SimpleDateFormat 类完成。常见使用方法:

方法一
public static  String formatDate(Date date)throws ParseException{
   
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }
    public static Date parse(String strDate) throws ParseException{
   
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }

------将创建对象变成私有,解决多线程问题,但加重了创建对象的负担。

方法二
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");   
    public static String formatDate(Date date)throws ParseException{
   
         return sdf.format(date);
    } 
    public static Date parse(String strDate) throws ParseException{
   
         return sdf.parse(date);
    }

------new SimpleDateFormat对象虽然变成全局变量,但在多线程下会出现日期转化报错、日期转化混乱问题。
问题原因:SimpleDateFormat是线程不安全的。在parse()方法传入的类成员变量,在多线程下Calendar对象的方法会造成数据不安全的问题。

最佳考虑方法,在多线程下,是否可以为每个线程分配对应的资源,防止出现竞争错误,ThreadLocal很好的解决了多线程并发问题。

private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
   
    protected DateFormat initialValue(){
   
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};
public static String currentDate() {
   
   return threadLocal.get().format(now());
}

private static ThreadLocal<DateFormat> threadLocal2 = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat(){
   
    DateFormat df = threadLocal2.get();
    if(df ==null){
   
        df = new SimpleDateFormat(DT_FORMAT);
        threadLocal2.set(df);
    }
    return df;
}

public static String currentDate() {
   
    return getDateFormat().format(now());
}

2.2、ThreadLocal实现原理

目的:保证当前线程中有自己的变量,不会出现多线程并发安全问题。
实现原理:
Thread为每个线程维护唯一的ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用Entry数组存储数据【将存储这个线程的所有ThreadLocal实例】;
Entry是继承弱引用,使用K-V方式组织数据,其中K是当前线程的不同ThreadLocal对象实例,V是存储的对象值;
主要方法是set、get、remove,都是先currentThread()获取自身线程,然后对ThreadLocalMap进行操作。不同线程拥有自身的ThreadLocalMap,因此实现“数据隔离”。

具体的对照Thread、ThreadLocal类的关系和下面的图表示

 Thread{
   
   ThreadLocal.ThreadLocalMap threadLocals = null;
 
 }
 
 ThreadLocal{
   
     static class ThreadLocalMap {
   
	     
		  static class Entry extends WeakReference<ThreadLocal<?>> {
   
		       //K-V方法存储数据。K是这个Thread线程的某个ThreadLocal的实例,V是对应数值
		      Entry(ThreadLocal<?> k, Object v) {
   
                super(k);
                value = v;
              }
		  }
	 }
 }

在这里插入图片描述

2.3、ThreadLocalMap

ThreadLocalMap是内部类,利用Entry数组存储数据,Entry是继承弱引用。
Entry使用K-V方式组织数据。
ThreadLocalMap的生命周期跟Thread(注意线程池中的Thread)一样长,如果没有手动删除对应key(线程使用结束归还给线程池了,其中的KV不再被使用但又不会GC回收,可认为是内存泄漏),一定会导致内存泄漏。
但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除【但没下一个的话,就不能清除】。
解决hash冲突—【hreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1及线性探测,寻找下一个相邻的位置。】

(1)、ThreadLocalMap如何解决hash冲突

ThreadLocalMap使用 线性探测的开发地址法(线性探测法) 解决hash冲突。当key存在hash冲突,会线性的往后探测直到找到为null的位置存入对象,或者找到key相同的位置覆盖更新原来数据。另外,在这过程中,如果发现Entry不为空但key为null的位置,会启动探测式清理法

2.4、为什么Entry的K使用弱引用?

作用:为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。
现有引用链条是:Thread—ThreadLcoalMap—Entry—k(弱引用)–V(强引用)

如果K是强引用:一个线程中,当ThreadLocal对象实例被用完,准备回收时,ThreadLcoalMap是拥有这个ThreadLocal的引用,那么这时候ThreadLocal是不能被回收,会造成K-V会一直在ThreadLcoalMap中存在,发生内存泄漏;
如果K是弱引用:一个线程中,当ThreadLocal对象实例被用完,准备回收时,由于K使用弱引用,当没其他对象的强关联引用下,ThreadLocal对象被回收,那么对于V来说【按照Java8特性】,在下一次ThreadLcoalMap调用set、get方法时会清空Entry的K为null的信息。

2.5、为什么Entry的V不设置为弱引用?

如果V是弱引用,它没被其他对象强关联,那么会被GC回收,这时候线程获取value时得到null结果。
V是我们真正想保存的数据,这个值不能无端被null,所以不能被弱引用。

2.6、ThreadLocal中内存泄漏问题(为什么会有内存泄漏)?

由于ThreadLoca

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamBoy_W.W.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值