【多线程编程】--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是内部类,利用E

猜你喜欢

转载自blog.csdn.net/xunmengyou1990/article/details/128985752