javaee之多线程之ThreadLocal

ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。

这个玩意有什么用处,或者说为什么要有这么一个东东?

先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile(这个关键字有点深,有时间看吧)这个关键字也是不能保证线程安全的。

那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。

应用场景

当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

DAO的数据库连接

这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。

我们从源码的角度来分析这个问题。

首先定义一个ThreadLocal:

public final class ConnectionUtil {

   private ConnectionUtil() {}

    private static final ThreadLocal<Connection> conn = new ThreadLocal<>();

    public static Connection getConn() {
        Connection con = conn.get();
        if (con == null) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
                con = DriverManager.getConnection("url", "userName", "password");
                conn.set(con);
            } catch (ClassNotFoundException | SQLException e) {
                // ...
            }
        }
        return con;
    }
}

这样子,都是用同一个连接,但是每个连接都是新的,是同一个连接的副本。

用户登录控制,如记录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;
}

实现机制

  1. 每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。
/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
  1. 当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值(返回的值是null,所以在进行get之前,必须先set,否则会报空指针异常,如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法)set到ThreadLocal中。
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}
  1. 当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

总结

  1. 每个Thread维护着一个ThreadLocalMap的引用

  2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

  3. ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。

  4. ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中

  5. 在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。

  6. ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

ThreadLocal内存泄漏问题

在这里插入图片描述
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用什么是弱引用,有点深,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏(内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。)

解决办法:使用完ThreadLocal后,执行remove(从ThreadLocal对象中删除一个值,根据键删除,键: 当前线程对象)操作,避免出现内存溢出内存溢出(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件,而由系统配置、数据流、用户代码等原因而导致的内存溢出错误,即使用户重新执行任务依然无法避免。情况。

部分内容引用自:
https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc
https://www.cnblogs.com/dreamroute/p/5034726.html
https://blog.csdn.net/qq_35029061/article/details/86495625

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页