ThreadLocal

一、何为ThreadLocal

threadLocal是线程存储信息的一种手段,方便我们在多线程环境下,分层(比如MVC)开发中存储信息的一种手段,比如存放登录用户信息,多租户环境下的来源信息,分库分表/读写分离的信息。

二、简单使用

首先,生成一个子线程,往ThreadLocal中存放一个thread A的信息,主线程无法获取到,再生成一个thread B同样无法获取到thread A,说明了ThreadLocal具有线程隔离性,但是线程B获取到了thread B说明同线程当中,是可以获取到的。

// 定义 ThreadLocal
        ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

        // 先放一个信息到ThreadLocal, 然后主线程再取,证明主线程不会访问到
        new Thread(() -> {
            stringThreadLocal.set("thread A");
        },"thread A").start();

        Thread.sleep(2000);

        System.out.printf("线程:%s,值:%s%n",Thread.currentThread().getName(),stringThreadLocal.get());
        new Thread(() -> {

            System.out.printf("线程:%s,值:%s%n",Thread.currentThread().getName(),stringThreadLocal.get());
            stringThreadLocal.set("thread B");
            System.out.printf("线程:%s,值:%s%n",Thread.currentThread().getName(),stringThreadLocal.get());
        },"thread B").start();

输出结果

线程:main,值:null
线程:thread B,值:null
线程:thread B,值:thread B

进程已结束,退出代码为 0

三、源码解读

3.1 构造方法

ThreadLocal类没有继承其他类或者实现任何接口,所以只有一个无参构造方法(其实也提供了一个附带初始化的静态初始化方法withInitial,内部需要自己实现初始化动作)

/**
  * Creates a thread local variable.
  * @see #withInitial(java.util.function.Supplier)
  */
 public ThreadLocal() {
 }

3.2 调用Set方法

下面是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);
     }
 }

首先使用Thread.currentThread()来获取当前线程对象,并通过getMap来获取当前线程的存储结构,通过查看getMap方法代码 ThreadLocalMap getMap(Thread t) { return t.threadLocals; }可以看到,这个map其实就是Thread类的threadLocals属性。

然后我们来看一下Thread类的一部分代码
在这里插入图片描述
发现这个存储结构定义在ThreadLocal类当中,并且初始值为null,自此大致清楚数据存储在线程当中,存储结构定义在ThreadLocal类当中.

3.2.1 createMap

当存储对象map为空时,我们需要对他进行初始化,并赋值给Thread.threadLocals属性

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

下面看一下这个ThreadLocalMap是如何进行初始化的

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

可以看到线程存储的信息是放在一个属性名为tableEntity数组当中,并且通过ThreadLocalINITIAL_CAPACITY(初始容量)进行按位与运算,得出该ThreadLocal变量存储的内容在线程对象内部属性threadLocals数组存储的下标。

3.2.2 set

这块不做深入解读了,大致内容就是遍历整个存储的map,如果内容已经存在就不进行处理了,如果不存在获取到下标,但是key==null,则进行特殊处理,如果没有存储过就加入并进行rehash和扩容。

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;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

四、内存泄漏

由于对象存储在线程当中,所以当线程使用完毕进行销毁时,存储的内容会随着线程进行销毁。
但是当我们使用线程池时,线程并不会被回收,这就可能会造成线程内存储的内容会一直存在。
回顾一下ThreadLocalMap当中的Entity数组

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

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

发现其中的key是一个弱引用,也就是说,当线程正在运行,而ThreadLocal超出作用域或者被重新赋值,导致其内存地址变更(hashCode改变)了,那么在此之前通过该对象在线程当中存储的内容进行游离状态,无法被访问,也不会被回收,自此造成内存泄漏,严重可导致内存溢出,抛出OOM异常,导致程序崩溃。

如何避免内存泄漏:使用完立刻调用remove方法,删除所使用的引用。

五、总结

一张图解释存储结构

在这里插入图片描述

看下存储动作

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值