ThreadLocal

什么是ThreadLocal?

源码注释:

This class provides thread-local variables.  These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable.  {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local
variable as long as the thread is alive and the {@code ThreadLocal}
instance is accessible; after a thread goes away, all of its copies of
thread-local instances are subject to garbage collection (unless other
references to these copies exist).

大致意思是:

此类提供线程局部变量。 这些变量不同于它们的普通对应变量,
因为每个访问一个(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。
ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)。
只要线程处于活动状态并且可以访问ThreadLocal实例,每个线程都持有对其线程局部变量副本的隐式引用; 线程消失后,它的所有线程本地实例副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

个人理解:

用于线程见的数据隔离,所作用的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,可以防止自己的变量被其它线程修改。

底层原理

源码:

public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取 ThreadLocalMap对象
        if (map != null) // 验证 map 是否为空
            map.set(this, value); // 不为空直接为set
        else
            createMap(t, value);// 为空创建一个map对象
    }

======================================================
//获取 ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
// 获取当前线程的 ThreadLocalMap
        return t.threadLocals;
    }
======================================================
	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
======================================================
public T get() {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果 ThreadLocalMap 为 null 设定初始化值
        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;
    }
=====================================================
private Entry getEntry(ThreadLocal<?> key) {
			// 获取hash值
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            // 判断当前位置不为空 && 是当前的 ThreadLocal,直接返回后再get方法中取出相应的值
            if (e != null && e.get() == key)
                return e;
            else
            // 不满足条件的继续找
                return getEntryAfterMiss(key, i, e);
        }

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
			// 同 getEntry中的判断 if (e != null && e.get() == key)
            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

这里我们可以看到,同时也明白了为什么 ThreadLocal 是用于线程见的数据隔离了,每个线程都有自己独有的 t h r e a d L o c a l s \color{purple}{threadLocals} threadLocals

ThreadLocalMap底层结构

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

在这里插入图片描述
由源码我们可以发现,Entry 继承了 WeekReference(弱引用)

为什么Entry要使用弱引用?

如 果 是 强 引 用 , 内 存 始 终 会 被 占 用 , 始 终 不 会 被 G C 线 程 回 收 , 从 而 导 致 了 内 存 泄 漏 的 问 题 \color{red}{如果是强引用,内存始终会被占用,始终不会被GC线程回收,从而导致了内存泄漏的问题} GC线

问题:

  • 为什么要用数组?
    结合实际应用,一个线程中可以同时设定多个ThreadLocal来存储不同类型的参数或对象,存放至
    ThreadLocalMap t h r e a d L o c a l s \color{purple}{threadLocals} threadLocals = nulll ;
    所以使用数组存储更合理
  • 怎么解决Hash冲突?
    源码:
    	/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         * 表格,根据需要调整大小。 table.length 必须始终是 2 的幂
         */
        private Entry[] table;
    	private void set(ThreadLocal<?> key, Object value) {
    
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
    
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
    
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
    			// 存在当前位置,替换对应的值
                if (k == key) {
                    e.value = value;
                    return;
                }
    			// 不存在当前位置,初始化一个 Entry 放置到 i 的位置
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
    
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    
      由上面源码我们可以看到每个 ThreadLocal 都会先获取对应的 Hash 值 
      int i = key.threadLocalHashCode & (len-1);,再定位 Entry 中的位置
      
      如果当前位置为空,则初始化一个 Entry 存放至对应的位置(i)
      如果当前位置不为空,将刷新当前位置的value
    

在这里插入图片描述

Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap中存储的Entry其中key是ThreadLocal对象本身,value则是要存储的对象

  • 如何共享线程ThreadLocal数据
//使用 InheritableThreadLocal 可以实现线程间 ThreadLocal 中数据的共享
ThreadLocal threadLocal = new InheritableThreadLocal();
'demo:'
static final ThreadLocal threadLocal = new InheritableThreadLocal();
    static {
        threadLocal.set("2");
    }

    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("1 + 1 = "+threadLocal.get());
        }).start();
    }

InheritableThreadLocal:输出:1 + 1 = 2
		   ThreadLocal:输出:1 + 1 = null
  • 共享的ThreadLocal数据是怎么传递的
    InheritableThreadLocal 继承于 ThreadLocal,我们在 ThreadLocal中可以看到
    在这里插入图片描述

    Thread.init()
    如果 inheritThreadLocals 为 true 且 父类线程的 inheritableThreadLocals 不为 null 创建一个共享ThreadLocal的 ThreadLocalMap ,将父线程的 inheritableThreadLocals 添加至新创建的ThreadLocalMap中,再将创建好的 ThreadLocalMap 重新赋值给当前线程的 inheritThreadLocals;
    在这里插入图片描述
    这里 createInheritedMap 中用的是一个私有方法
    在这里插入图片描述

    • 内存泄漏
      因为弱引用的关系,弱引用的特性这里就不再赘述了,详情可见(强、软、弱、虚引用这篇文章)。
      出现场景大致为:当创建ThreadLocal的线程一直持续运行,Entry中的value就可能一直不能被回收,从而导致内存泄漏。
      解决办法:在ThreadLocal使用完时调用remove方法,将当前线程创建的ThreadLocal进行清除,便于GC的回收
实战运用:

说了这么多那么到底在什么场景下使用呢?
以Spring为例:
Spring在事物隔离级别上运用的就是 ThreadLocal 如下图所示,每个线程自己的链接都是依附于自己ThreadLocal来进行保存的
在这里插入图片描述

声明:此篇文章借鉴了 知乎-敖丙

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值