ThreadLocal

1、ThreadLocal的使用场景

  ThreadLocal主要用来存储线程的本地数据,做到线程数据的隔离。常用的方法有:set()get()remove()分别对应存储、获取和删除。下面看几个使用场景:

场景1:在拦截器中的使用

//将其封装在工具类中
public class ContextUtils {
    public static final ThreadLocal<UserInfo> USER_INFO_THREAD_LOCAL = new ThreadLocal();
}
//执行前 存储
public boolean pretHandle(HttpServletRequest request)  {
    //解析token获取用户信息
    String token = request.getHeader("token");
    UserInfo userInfo = parseToken(token);   
    //存入
    ContextUtils.USER_INFO_THREAD_LOCAL.set(userInfo);
    
    return true;
}


//执行后 删除
public void postHandle(HttpServletRequest request)  {
    ContextUtils.USER_INFO_THREAD_LOCAL.remove();
}

  使用

//提交订单
public void orderSubmit(){
    //获取用户信息
    UserInfo userInfo = ContextUtils.USER_INFO_THREAD_LOCAL.get();
    //下单
    submit(userInfo);
    //删除购物车
    removeCard(userInfo);
}

场景2:时间格式化类 SimpleDateFormat,它是线程不安全的,多线程下使用同一个实例会出现数据错误,每次使用都创建又比较浪费内存。

public class DateFormatThreadLocal {
 
    private static final ThreadLocal<SimpleDateFormat> threadLocalFormatter = ThreadLocal.withInitial(() -> {
        // 初始化 SimpleDateFormat 实例
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    });
 
    public static Date parseDate(String dateStr) throws ParseException {
        SimpleDateFormat formatter = threadLocalFormatter.get();
        return formatter.parse(dateStr);
    }
 
    public static String formatDate(Date date) {
        SimpleDateFormat formatter = threadLocalFormatter.get();
        return formatter.format(date);
    }
}

场景3:数据库读写分离,利用ThreadLocal保存数据源标识,使同一个线程使用同一个数据源。

public class DataSourceContextHolder {
 
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
 
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }
 
    public static String getDataSourceType() {
        return contextHolder.get();
    }
 
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}
 
public class DataSourceRoutingDataSource extends AbstractRoutingDataSource {
 
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}
 
// 使用DataSourceContextHolder设置数据源类型
DataSourceContextHolder.setDataSourceType("write"); // 设置为写数据源
// 执行数据库写操作
DataSourceContextHolder.setDataSourceType("read"); // 设置为读数据源
// 执行数据库读操作
DataSourceContextHolder.clearDataSourceType(); // 清除数据源类型

  DataSourceContextHolder类使用ThreadLocal保存当前线程所需的数据源类型。DataSourceRoutingDataSource类继承自AbstractRoutingDataSource,并重写了determineCurrentLookupKey方法,该方法根据DataSourceContextHolder保存的数据源类型来决定最终使用哪个数据源。这样,在同一线程内,无论执行读操作还是写操作,都能保证使用正确的数据源。

2、ThreadLocal 原理

  这是ThreadLocal中两个内部类关系定义:

public class ThreadLocal<T> {
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } } }

  这是Thread中持有ThreadLocalMap的定义:

public class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null;
} 

  ThreadLocal是一个壳子,真正的存储结构是ThreadLocal里ThreadLocalMap内部类,每个Thread对象维护着一个ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用 Entry(k,v) 来进行存储。

  ThreadLocal的set()方法实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值Value是传递进来的对象;ThreadLocal的get()方法实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象。

  ThreadLocal本身并不存储值,它只是自己作为一个key来让线程从ThreadLocalMap获取value,正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响。

3、ThreadLocal 使用弱引用以及内存泄漏问题

  了解了他们之间的定义关系之后,我们再来看一下ThreadLocal实例在线程中的引用关系:

    

  看到这个引用关系我们就知道,Entry有两个引用关系,一个是与value的引用,一个是与key—ThreadLocal实例的引用,其中与ThreadLocal实例的应用为弱引用。

  这个弱引用,保证当Entry没有被清除的情况下,ThreadLocal也能被回收,但是如果不手动清理调value的话,也可能造成内存泄漏,因为ThreadLocalMap的生命周期一致。所以导致内存泄漏的根源是:没有手动清除Entry与value的引用关系。 

  但是ThreadLocal的 set/get/remove 也有判断当 threadLocal 作为key为null 时,也会自动删除对应的value。

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

 

ThreadLocal的原理、使用注意的地方(可能会导致内存泄漏)

参考文章:1、https://www.cnblogs.com/zxporz/p/10900852.html

                  2、https://zhuanlan.zhihu.com/p/102571059

 

posted @ 2021-01-21 21:10  jingyi_up  阅读(51)  评论(0编辑  收藏  举报