当前位置:网站首页>ThreadLocal的简单理解

ThreadLocal的简单理解

2022-08-09 11:47:00 头顶假发

目录

  • 二、ThreadLocal解决的问题
  • 三、如何创建一个ThreadLocal实例
  • 四、ThreadLocal如何做到线程变量隔离
    • 2、看下set方法是如何实现的
    • 3、看看 get 方法如何实现
  • 五、ThreadLocalMap中的hash冲突是如何处理的
    • 1、ThreadLocal对象的hash值是怎样的
  • 六、ThreadLocal内存泄漏

一、背景

最近有人问我 ThreadLocal 是如何做到在每个线程中的值都是隔离的,此处写篇文章来简单记录下。

二、ThreadLocal解决的问题

  1. 该数据属于该线程 Thread 自身,别的线程无法对其影响。 (需要注意:需要调用ThreadLocal的remove方法)
  2. 不存在线程安全问题。 (因为 ThreadLocal 类型的变量只有自身的线程可以访问,所以这点是成立的。)

比如:

用户登录成功后,需要将 登录用户信息 保存起来,以方便在系统中的任何地方都可以使用到,那么此时就可以使用 ThreadLocal 来实现。例如: Spring Security 中的 ThreadLocalSecurityContextHolderStrategy 类。

三、如何创建一个ThreadLocal实例

private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();

ThreadLocal的实例推荐使用 private static final 来修饰。

四、ThreadLocal如何做到线程变量隔离

1、理解3个类

  1. ThreadLocal : 此类提供了一个简单的 set , get , remove 方法,用于设置,获取或移除 绑定到线程本地变量中的值。

  2. ThreadLocalMap : 这是在ThreadLocal中定义的一个类,可以简单的将它理解成一个Map,不过它的key是WeakReference弱引用类型,这样当这个值没有在别的地方引用时,在发生垃圾回收时,这个map的 key 会被自动回收,不过它的值不会被自动回收。

    static class Entry extends WeakReference<ThreadLocal<?>> {
    	Object value;
    	Entry(ThreadLocal<?> k, Object v) {
    		// key 弱引用
    		super(k);
    		// 值强引用
    		value = v;
    	}
    }
  3. Thread :这个是线程类,在这个类中存在一个 threadLocals 变量,具体的类型是 ThreadLocal.ThreadLocalMap 。

2、看下set方法是如何实现的

public void set(T value) {
	// 获取当前线程
	Thread t = Thread.currentThread();
	// 获取绑定到这个线程自身的 ThreadLocalMap,这个ThreadLocalMap是从Thread类的`threadLocals`变量中获取的
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		// 向map中设置值,key为 ThreadLocal 对象的实例。
		map.set(this, value);
	} else {
		// 如果map不存在,则创建出来。
		createMap(t, value);
	}
}

通过上方的代码,我们可知: 当我们向ThreadLocal中设置一个值,会经过如下几个步骤:

  1. 获取当前线程 Thread
  2. 获取当前线程的 ThreadLocalMap 对象。
  3. 向 ThreadLocalMap 中设置值,key为 ThreadLocal 对象,值为具体的值。

3、看看 get 方法如何实现

public T get() {
	// 获取当前线程
	Thread t = Thread.currentThread();
	// 获取这个线程自身绑定的 ThreadLocalMap 对象
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		// this是ThreadLocal对象,获取Map中的Entry对象
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			// 获取具体的值
			T result = (T)e.value;
			return result;
		}
	}
	// 设置初始值
	return setInitialValue();
}

从上方的get 和 set 方法可以得知,通过往ThreadLocal对象中设置值或获取值,其实是最终操作到Thread对象中的threadLocals字段中,而这个字段是Thread自身的,因此做到了隔离。

五、ThreadLocalMap中的hash冲突是如何处理的

1、ThreadLocal对象的hash值是怎样的

private final int threadLocalHashCode = nextHashCode();
	// 该 ThreadLocal 对象自身的hash code值
    private final int threadLocalHashCode = nextHashCode();
	// 从0开始
    private static AtomicInteger nextHashCode = new AtomicInteger();
	// 每次递增固定的值
    private static final int HASH_INCREMENT = 0x61c88647;
	// hash code 值计算
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

从上方的代码中可以, ThreadLocal 类在实例化出来之后,它的 hash code值(threadLocalHashCode) 就是固定的,即使 ThreadLocal 调用了 set 方法,设置了别的值,它的 hash code值 也不会发生变化。

此字段 threadLocalHashCode 为 ThreadLocal 对象的hash值,在 ThreadLocalMap 中需要用到这个hash值。

2、解决hash冲突

 ThreadLocalMap 解决hash冲突的办法很简单。就是通过线性探测法。如果发生了冲突,就去找数组后面的可用位置。具体看上图。演示的是A和B 2个ThreadLocal对象,然后发生了冲突,A和B存在的位置在那个地方。

六、ThreadLocal内存泄漏

ThreadLocal为什么会存在内存泄漏呢?

这是因为 ThreadLocalMap 中的 key 是 WeakReference 类型,也就是弱引用类型,而弱引用类型的数据在没有外部强引用类型的话,在发生 gc 的时候,会自动被回收掉。 注意: 此时是 key 被回收了,但是 value 是没有回收的。因此在 ThreadLocalMap 中的 Entry[] 中可能存在 key 是 null ,但是 value 是具体的值的对象,因此就发生了内存泄漏。

解决内存泄漏:

当我们使用完 ThreadLocal 对象后,需要在适当的时机调用 ThreadLocal#remove() 方法。 否则就只有等 Thread 自动退出才能清除,如果是使用了线程池, Thread 会重用,清除的机会就更难。

原网站

版权声明
本文为[头顶假发]所创,转载请带上原文链接,感谢
https://blog.csdn.net/lt_xiaodou/article/details/126243535