ThreadLocal


1 简介

  ThreadLocal提供了线程内存储变量的能力。

2 ThreadLocal的应用场景

如下图,方法1调用方法2,方法2调用方法3,方法3调用方法4

如果我们想要在方法4中使用方法1中的一个变量sa,可以怎么做?

1)通过参数传递

  在某些情况下可以,但是如果中间某个方法(如method2)是其它人提供的api,我们不能去更改这个方法的时候,就不能通过参数传递

2)把sa提出来定义为全局变量

  这样子虽然可以访问,但是会有线程安全问题

3)使用ThreadLocal

3 ThreadLocal的实际应用

  在使用spring的过程中,我们进行事务管理,通常会使用@Transactional注解。它就是应用了ThreadLocal

 同样的,如上图,方法一层层调用,要保证几个方法事务,那么它们几个方法必须使用同一个连接。不能方法1使用连接1,方法2使用连接2,这样子没有办法保证事务。那么各个方法怎么样获取到同一个连接呢,spring就使用了ThreadLocal

4 示例

4.1 代码

public class ThreadLocalTest {

    public static void main(String[] args) {
        ThreadLocal tl = new ThreadLocal<>();

        new Thread(()->{

            tl.set(new PojoTest());
            System.out.println(tl.get());

        }).start();

        new Thread(()->{

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(tl.get());

        }).start();

    }
}

4.2 执行结果

com.ruoyi.weixin.user.AopTest.PojoTest@75e3668f
null

4.3 执行说明

1)创建了一个ThreadLocal

2)在第一个线程中给这个ThreadLocal对象set一个PojoTest对象

3)在第一个线程中get去获取PojoTest对象,获取到了

4)但是在第二个线程中去获取的时候没有获取到

说明ThreadLocal里面存储的对象是线程隔离的

5 源码(set方法)

5.1 set方法

public void set(T value) {
    //获取当前线程 Thread t
= Thread.currentThread();
    //获取一个ThreadLocalMap ThreadLocalMap map
= getMap(t); if (map != null)
    //在ThreadLocalMap以键值对的形式存入ThreadLocal对象和value值,注意key是ThreadLocal对象而不是线程 map.
set(this, value); else createMap(t, value); }Thread t = Thread.currentThread();

5.1.1 getMap()方法

getMap方法很简单,直接返回了线程对象的一个变量threadLocals

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

5.1.2 再看一下threadLocals这个变量

发现它的类型是一个ThreadLocal的内部类ThreadLocalMap

ThreadLocal.ThreadLocalMap threadLocals = null;

也就是说getMap方法是获取到Thread的一个类型为ThreadLocalMap的变量

5.1.3 ThreadLocalMap的set方法

map.set(this, value);
private void set(ThreadLocal<?> key, Object value) {
            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();
        }

这个方法中,其它代码不用看,看最关键的一行代码,创建了一个Entry对象,它就是一个键值对对象

tab[i] = new Entry(key, value);
static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

Entry继承WeakReference(弱引用),WeakReference继承Reference,Reference中有一个成员变量:private T referent;

也就是说Entry有两个变量:T referentObject value

关于弱引用:https://www.cnblogs.com/jthr/p/15960725.html

我们看Entry的构造方法

1)super(k);

这行代码调用父类WeakReference的构造方法,使用ThreadLocal创建了一个WeakReference对象,并把它赋值给了referent

2)value = v;

把存储对象赋值给value

5.2 存储关系图

ThreadLocal.set(x)

实际上是把数据存储到了当前线程的成员变量ThreadLocalMap threadLocals

ThreadLocalMap中维护了一个Entry数组

Entry里面存储KEY-VALUE键值对,KEY是ThreadLocal的WeekReference对象,VALUE是要存储的对象

 6 为什么要使用弱引用

 我们创建了一个ThreadLocal对象,并调用了set方法

那么这个ThreadLocal对象就会有两个引用,一个是t指向它的一个强引用,一个是Entry的key(referent属性)指向它的一个弱引用。

当我们把t设置为null的时候,在垃圾回收的时候,这个ThreadLocal由于只有一个弱引用指向它,就会被回收

如果不使用弱引用,那么Entry的key(referent属性)指向它的就是一个强引用,即便我们把t设为null,它也不会被回收。只有在这个线程清空key(spring中线程使用完回到线程池会清空threadLocals)或者线程本身被销毁的时候,ThreadLocal才能被回收