• 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<PojoTest> 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<ThreadLocal<?>> {
                /** 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才能被回收

  • 相关阅读:
    简单易懂dubbo入门实例
    Java中String和byte[]间的 转换
    优秀项目
    Linux下命令行安装weblogic10.3.6
    Linux中VMware虚拟机增加磁盘空间的扩容操作
    office2016_windows永久激活查看方法
    解决eclipse报PermGen space异常的问题
    Ubuntu 18.04上nginx+php环境搭建
    git hook之commit-msg用于检测提交时间是否正确
    maven随记
  • 原文地址:https://www.cnblogs.com/jthr/p/15966008.html
Copyright © 2020-2023  润新知