ThreadLocal使用

作者: 无心火 | 来源:发表于2019-11-10 17:11 被阅读0次

1.前言

相信对java框架有一定了解的话,都会知道ThreadLocal的存在。但对于大部分开发者来说,从头接触ThreadLocal的机会比较少,因为ThreadLocal在框架前期设计时一般会提供相应的工具类供调用方使用。对于普通开发者,在功能开发时接触的是封装好的工具类,但并不一定知道其调用的工具类本质用到ThreadLocal。
ThreadLocal是一个很实用的工具类。在我们日常Java开发中难免遇到需要把一个参数层层的传递到最内层,然而中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个通用的参数。如果有一个办法能够在任何一个类里面想用的时候直接拿来使用就太好了。Java的Web项目大部分都是基于Tomcat,每次访问都是一个新的线程,这样可以使我们用到了ThreadLocal,每一个线程都独享一个ThreadLocal,在接收请求的时候set特定内容,在需要的时候get这个值。

2.原理

ThreadLocal又称线程本地变量,它是一个数据结构,有点像HashMap,可以保存"key : value"键值对,用于实现线程内部的数据共享叫线程共享(对于同一个线程内部数据一致)。即相同的一段代码多个线程来执行,每个线程使用的数据只与当前线程有关,各个线程的数据互不干扰。

ThreadLocal内部结构图:


ThreadLocal.jpg

从上面的结构图,ThreadLocal的核心机制:

(1)每个Thread线程内部都有一个Map。

(2)Map里面存储线程本地对象(key)和线程的变量副本(value)但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

ThreadLocal类接口很简单,只有4个方法

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
  • void set(Object value)
    设置当前线程的线程局部变量的值。

  • public Object get()
    该方法返回当前线程所对应的线程局部变量。

  • public void remove()
    将当前线程局部变量的值删除,目的是为了减少内存的占用,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。但实际项目开发中,使用完ThreadLocal之后,建议显示调用remove方法,避免可能发生内存泄露。

  • protected Object initialValue()
    返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

3.使用场景

ThreadLocal最常见使用场景可用来解决数据库连接、Session用户管理等。

数据库连接

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(DB_URL);  
    }  
};  
  
public static Connection getConnection() {  
    return connectionHolder.get();  
}

session管理

private static final ThreadLocal threadSession = new ThreadLocal();  
 public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}

通用使用


public class ThreadLocalContext {
    
    private static ThreadLocal<Map<Class<?>, Object>> context = new InheritableThreadLocal<>();
    
    /**
     * 把参数设置到上下文的Map中
     */
    public static void put(Object obj) {
        Map<Class<?>, Object> map = context.get();
        if (map == null) {
            map = new HashMap<>();
            context.set(map);
        }
        if (obj instanceof Enum) {
            map.put(obj.getClass().getSuperclass(), obj);
        } else {
            map.put(obj.getClass(), obj);
        }
    }
    
    /**
     * 从上下文中,根据类名取出参数
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(Class<T> c) {
        Map<Class<?>, Object> map = context.get();
        if (map == null) {
            return null;
        }
        return (T) map.get(c);
    }
    
    /**
     * 清空ThreadLocal的数据
     */
    public static void clean() {
        context.remove();
    }
}

4.总结

ThreadLocal避免了在调用每一个方法时都要传递执行上下文信息,合理的使用ThreadLocal可以起到事倍功半的效果,但是需要避免滥用。
ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险,使用时需注意。

相关文章

网友评论

    本文标题:ThreadLocal使用

    本文链接:https://www.haomeiwen.com/subject/uzeabctx.html