一文带你彻底搞懂ThreadLocal

前言

共享变量一直是并发中的老大难问题,每个线程都对它有操作权,所以线程之间的同步很关键,锁也就应运而生。这里换一个思路,是否可以把共享变量私有化?即每个线程都拥有一份共享变量的本地副本,每个线程对应一个副本,同时对共享变量的操作也改为对属于自己的副本的操作,这样每个线程处理自己的本地变量,形成数据隔离。事实上这就是ThreadLocal了。

就线程同步而言,锁可以认为是时间换空间,ThreadLocal可以认为是空间换时间,其实个人觉得这么描述有点强行往同步靠的意思,专业的人干专业的事,同步这个还是就老老实实交给锁吧。 ThreadLocal最适合的是变量在线程间隔离而在方法或类间共享的场景。

ThreadLocal的应用场景

ThreadLocal在Spring中事务的应用

我们可能每天都在使用Spring写dao写service,真的是一个非常爽的框架.我们能用这么爽是因为Spring在底层把脏活儿累活儿全干了。在一个Service中我们可能要写很多个dao,如果多个dao都用不同的JDBC连接,很费时费力费资源不说,事务性就得不到保证了,因为我们知道事务需要在一个连接内才能得以实现。事务对应着连接,所以如果我们每个线程对应一个连接,也就能保证我们在一个service中很爽的叨叨叨了,Spring底层正是使用ThreadLocal对连接进行了封装,可劲儿叨吧你就

ThreadLocal在日志中的应用

slf4j中有一个类叫MDC,全名叫Mapped Diagnositc Context,这个类基于 InheritableThreadLocal ,先不细说,暂且把它当成ThreadLocal。我们可以给每个服务或用户行为编号,每次客户调用服务就把编号写到threadlocal包含的变量中,最后打印出来。似乎有点dubbo链路监控的意思(然而我dubbo才学会拼写没多久,没看过源码就不妄议了hhh)

看完应用场景似乎对Threadlocal已经有所了解,不过,本着知其然并知其所以然的心态,还是去看看源码怎么实现的吧
一文带你彻底搞懂ThreadLocal_第1张图片

源码分析

ThreadLocalMap

先去Thread类中瞄一眼,Thread中有2个成员变量

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

两个变量都是ThreadLocalMap类,咦?说了半天 ThreadLocal怎么又冒出来个ThreadLocalMap
一文带你彻底搞懂ThreadLocal_第2张图片

是这样子的,默认这两个变量都是null,正如我们现在看到的。

一旦我们调用ThreadLocal的set或者get才会真正创建他们。也就是你以为你把变量交给ThreadLocal了,其实这小子转手就给ThreadLocalMap了,ThreadLocal就是套在 ThreadLocalMap外面的一层壳而已。
一文带你彻底搞懂ThreadLocal_第3张图片

ThreadLocal的组成如下
一文带你彻底搞懂ThreadLocal_第4张图片
可以看出,就跟map基本一样,key是ThreadLocal的引用,value则是由开发者设置,即本地变量

ThreadLocal

set

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 这里this是 ThreadLocal的实例引用
            map.set(this, value);
        else
            createMap(t, value);
}

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}
  1. 首先获取当前线程
  2. 以当前线程为key去查找当前线程的map即threadLocals变量
  3. 如果threadLocals不为空,就把ThreadLocal引用作为key,value传给map
  4. 否则创建map,也就是初始化当前线程的threadLocals变量

再次注意值存放的实际位置是Thread中的ThreadLocalMap变量,ThreadLocalMap是一个map,key是ThreadLocal的实例引用,值则是我们要存的变量

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

其实就很简单了,set的反过程嘛,先得到当前线程,然后得到成员变量threadLocals,如果threadLocals不为空,返回本地变量对应值,否则初始化threadLocals

初始化threadLocals

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

protected T initialValue() {
        return null;
}

判断当前threadLocals是否为空,如果不为空,设置当前ThreadLocal的实例引用对应变量为null,否则调用createMap创建threadLocals变量

remove()

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

如果threadLocals变量不为空,删掉map中当前ThreadLocal对应实例引用的本地变量。

ThreadLocal的内存溢出问题

从上边一路走下来我们应该了解了,每一个线程中都有一个ThreadLocalMap
类型的threadLocals变量,这个map中key为ThreadLocal的实例引用,value为对应的本地变量。如果这个线程不消亡,开发者也没有采用remove操作及时清除掉不再使用的变量,这些变量就会一直存在map中,直到撑爆你的内存,造成内存溢出问题

ThreadLocal的继承问题

假设一个场景,我们需要异步起一个线程发送邮件给用户,子线程需要打日志,那么就需要父线程中本地变量,这里子线程可以通过父线程的ThreadLocal得到父线程设置的的本地变量吗?答案是不可以,ThreadLocal不支持父子线程间的继承传递

这里可以使用InheritableThreadLocal,没错就是上边日志那块我们提到过的。它是ThreadLocal的子类,其实用法跟ThreadLocal完全一样,不过,它可以使得子线程访问在父线程中设置的变量,简单看下这一实现的原理

首先看下线程的构造方法,说实话之前真没看过这个构造方法,我觉得应该很多人都没看过吧
一文带你彻底搞懂ThreadLocal_第5张图片

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
}

这个init方法追进去,你会看到底下这行代码,init方法体太长,我就只截取出来这最关键的一部分

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

parent就是父线程,如果父线程中的inheritableThreadLocals 不为空,就执行 createInheritedMap

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
}

这个代码就不继续展开了,过程就是把父线程中的 inheritableThreadLocals 的值全部复制一份到子线程的 inheritableThreadLocals 中

总结

  1. 每个线程对应一个ThreadLocalMap,ThreadLocal其实就是套在ThreadLocalMap上的一层壳

  2. ThreadLocalMap的key是ThreadLocal的实例引用,value是我们像设置的本地变量

  3. 若是线程一直不停,threadLocalMap中的本地变量就会越来越多,注意即使remove掉不再使用的变量,防止内存溢出


公众号
一文带你彻底搞懂ThreadLocal_第6张图片

你可能感兴趣的:(java多线程)