ThreadLocal原理及使用场景分析

ThreadLocal是Java中用于解决线程安全问题的工具,它通过为每个线程创建变量副本实现数据隔离。在set、get和remove方法中,ThreadLocal维护着线程内的ThreadLocalMap。未被外部强引用的ThreadLocal在GC时会被回收,但其value因强引用可能引发内存泄露,需手动remove。适用场景包括方法参数传递、Spring事务管理等。
摘要由CSDN通过智能技术生成

一、ThreadLocal定义
ThreadLocal叫做线程局部变量,也就是说ThreadLocal中的变量是属于当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,因此这种方式可以解决线程访问的安全问题。

二、ThreadLocal如何解决线程安全问题
1.ThreadLocal和Synchonized都用于解决多线程并发访问。
2.相比于Synchronized,ThreadLocal通过实现线程间的数据隔离来保证多线程并发安全问题,而Synchronized则通过实现锁机制,使变量或代码块在同一时刻该只能被一个线程访问,这样就隔离了多个线程对数据的数据共享,以此来保证线程安全。

总结来说:ThreadLocal是作为当前线程属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadLocal,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。

三、ThreadLocal原理
原理我们可以结合源码分析:
ThreadLocal 中主要涉及三个方法,set、get、remove。

1.set方法:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

ThreadLocal set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化,因此每个线程都有独立的threadLocalMap。

2.get方法:

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

ThreadLocal get首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则获取对应的变量值,若为空,则会设置一个初始化的值。

3.remove方法:

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

remove方法,直接将ThrealLocal 对应的值从当前Thread中的ThreadLocalMap中删除。为什么要删除,这涉及到内存泄露的问题。

实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。

所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,因为value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何解决呢?
解决办法太简单了,在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。

三、ThreadLocal使用场景
1.场景一:在重入方法中替代参数的显式传递方式
​ 假如在我们的业务方法中需要调用其他方法,同时其他方法都需要用到同一个对象时,可以使用ThreadLocal替代参数的传递或者static静态全局变量。这是因为使用参数传递造成代码的耦合度高,那么使用静态全局变量在多线程环境下不安全。当该对象用ThreadLocal包装过后,就可以保证在该线程中独此一份,同时和其他线程隔离。

例如在Spring的@Transaction事务声明的注解中就使用ThreadLocal保存了当前的Connection对象,避免在本次调用的不同方法中使用不同的Connection对象。

2.场景二:在Spring中的实例分析DateTimeContextHolder类
,里面用了ThreadLocal存储时间的上下文

public final class DateTimeContextHolder {

	private static final ThreadLocal<DateTimeContext> dateTimeContextHolder =
			new NamedThreadLocal<>("DateTimeContext");


	private DateTimeContextHolder() {
	}


	/**
	 * Reset the DateTimeContext for the current thread.
	 */
	public static void resetDateTimeContext() {
		dateTimeContextHolder.remove();
	}

	/**
	 * Associate the given DateTimeContext with the current thread.
	 * @param dateTimeContext the current DateTimeContext,
	 * or {@code null} to reset the thread-bound context
	 */
	public static void setDateTimeContext(@Nullable DateTimeContext dateTimeContext) {
		if (dateTimeContext == null) {
			resetDateTimeContext();
		}
		else {
			dateTimeContextHolder.set(dateTimeContext);
		}
	}

	/**
	 * Return the DateTimeContext associated with the current thread, if any.
	 * @return the current DateTimeContext, or {@code null} if none
	 */
	@Nullable
	public static DateTimeContext getDateTimeContext() {
		return dateTimeContextHolder.get();
	}

3.存储用户的session
例子:

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

这样就实现了session的存储。

四、ThreadLocal简单使用

public class ThreadLocalDemo {
 
    private static ThreadLocal<String> local = new ThreadLocal<String>();
 
    static void print(String s) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + local .get());
        //清除本地内存中的本地变量
        local .remove();
    }
    public static void main(String[] args) throws InterruptedException {
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocalDemo.local.set("localOne");
                print("One");
                //打印本地变量
                System.out.println("after remove : " + local .get());
               
            }
        },"One").start();
 
        Thread.sleep(1000);
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("localTwo");
                print("Two");
                System.out.println("after remove : " + local .get());
              
            }
        },"Two").start();
    }
}
结果
One :localOne
after remove : null
Two :localTwo
after remove : null

大家看完应该会有所理解。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜空下的星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值