• ThreadLocal的简单理解


    一、背景

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

    二、ThreadLocal解决的问题

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

    比如:

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

    三、如何创建一个ThreadLocal实例

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

    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;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过上方的代码,我们可知: 当我们向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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    从上方的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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

    2、解决hash冲突

    在这里插入图片描述

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

    六、ThreadLocal内存泄漏

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

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

    解决内存泄漏:

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

  • 相关阅读:
    Git上传项目到Github
    day07-缓存套餐
    nginx - 使用nginx部署vue项目到本地以及代理和负载均衡
    P1059 [NOIP2006 普及组] 明明的随机数
    c语言宏相关高级用法
    vue自动跳转模拟登陆
    系统设计的11个考虑
    【论文笔记】基于深度学习的移动机器人自主导航实验平台
    Jenkins 安全 tips
    入坑机器学习:四,单变量线性回归
  • 原文地址:https://blog.csdn.net/fu_huo_1993/article/details/125497445