20210419_ThreadLocal 详解

本文详细介绍了ThreadLocal的概念、工作原理及应用场景,包括如何避免内存泄漏等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、ThreadLocal是什么

  ThreadLocal是JDK提供的,线程本地变量。也就是如果创建了一个ThreadLocal变量,那么访问这个变量的所有线程都会有这个变量的本地拷贝,多个线程操作这个变量的时候,实际上是操作本地变量的拷贝。实现了多个线程中变量的隔离,避免了线程安全的问题。

  一个ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的(每一个线程只能看到自己线程的值)

二、ThreadLocal原理

  ThreadLocal存储的值,并不是在ThreadLocal实例中,而是存储在每一个Thread实例的ThreadLocalMap中。ThreadLocalMap为Thread类的一个变量。

public class Thread implements Runnable {
    //......
    ThreadLocalMap threadLocals;
    ThreadLocalMap inheritableThreadLocals;
   //......
}

  我们通过ThreadLocal的set方法进行分析。

    public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前使用ThreadLoacal实例的线程
        ThreadLocal.ThreadLocalMap map = this.getMap(t); //获取当前线程的ThreaLocalMap对象
        if (map != null) {
            map.set(this, value);//将值value存储在当前线程的map中
        } else {
            this.createMap(t, value);//创建线程,并将值value存储到当前线程的ThreaLocalMap中
        }

    }
		//获取当前线程的ThreadLoacalMap对象
    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
		//创建当前线程的ThreadLocalMap对象
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

  我们可以简单的把ThreadLocalMap理解成Map对象,key为当前的ThreadLocal对象,value则可以是任何的。当我们使用ThreadLocal对象set一个新的值的时候。首先会获取该线程的ThreadLoacalMap对象,如果能够获取到则设置该变量,如果获取不到则创建,并设置。而这些变量,都是属于具体的某一个Thread实例的。不同的线程之间这些变量都是隔离的。

三、ThreadLocal怎么用

  根据上面的总结,使用场景可以概括为:每个线程需要单独的变量实例,且这些实例不能被其他线程所共享。

  1. 线程安全
public class DateUtil {
    private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static String formatDate(Date date) {
        return format1.get().format(date);
    }
}

  因为,SimpleDateFormat是线程非安全的,在多线程的环境中,直接调用同一个DateFormat实例的时候,就会出现线程不安全现象。使用ThreadLocal则可以规避此问题。

  1. 存储用户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;
        }
    
  2. 存储数据库连接

  3. 进行事务操作,用于存储线程事务信息。

四、ThreadLocal内存泄露问题

在这里插入图片描述

  上图详细的展示了ThreadLocal变量的引用关系。阐述了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

  1. Thread中有个map,就是ThreadLocalMap
  2. ThreadLocalMap的key是ThreadLocal,值为任意类型
  3. ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
  4. 突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏

  解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

  原因:如果ThreadLocal没有外部强引用,那么在发生垃圾回收的时候,ThreadLocal就必定会被回收,而ThreadLocal又作为Map中的key,ThreadLocal被回收就会导致一个key为null的entry,外部就无法通过key来访问这个entry,垃圾回收也无法回收,这就造成了内存泄漏

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值