首页 > 其他分享 >ThreadLocal和InheritableThreadLocal详解,基本原理及注意项 父子线程数据共享

ThreadLocal和InheritableThreadLocal详解,基本原理及注意项 父子线程数据共享

时间:2023-12-22 18:44:41浏览次数:36  
标签:变量 Thread ThreadLocalMap 数据共享 ThreadLocal 线程 InheritableThreadLocal null

一、ThreadLocal介绍

在多线程环境下访问同一个线程的时候会出现并发问题,特别是多个线程同时对一个变量进行写入操作时,为了保证线程的安全,通常会进行加锁来保证线程的安全,但是加锁又会造成效率的降低;ThreadLocal是jdk提供的除了加锁之外保证线程安全的方法,其实现原理是在Thread类中定义了两个ThreadLocalMap类型变量threadLocals、inheritableThreadLocals用来存储当前操作的ThreadLocal的引用及变量对象,这样就可以把当前线程的变量和其他的线程的变量之间进行隔离,从而实现了线程的安全性。

在使用ThreadLocal时,需要把主线程中的ThreadLocal值传输到子线程(线程池维护)中,故使用了InheritableThreadLocal作为传输。后发现,主线程执行ThreadLocal.remove()后,子线程中的ThreadLocal并不会被remove(),导致线程池中维护的ThreadLocal存储的值一直不变。于是深入进行了研究。

二、ThreadLocal的简单示例

首先定义一个ThreadLocal全局变量,在main方法中开启两个线程,在线程启用之前先设置变量然后再线程内部和外部获取变量,发现输出结果有什么不同吗?疑问我们后面解答。

public class Test1 {
    public static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InterruptedException {
        threadLocal.set("主线程1。。。");
        System.out.println("线程1:" + threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程1:" + Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        }).start();

        Thread.sleep(1000);
        threadLocal.set("主线程2。。。");
        System.out.println("线程2:" + threadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程2:" + Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        }).start();
        //删除本地内存中的变量
        threadLocal.remove();
    }

}

输出结果如下:

线程1:主线程1。。。
子线程1:Thread-0:null
线程2:主线程2。。。
子线程2:Thread-1:null

三、ThreadLocal原理及源码分析
Thread类有两个变量threadLocals和inheritableThreadLocals,这两个变量都是ThreadLocal类的内部类ThreadLocalMap类型变量,ThreadLocalMap是一个类似于HashMap类型的容器类,默认情况下两个变量都为null,只有当第一次调用ThreadLocal的get或set方法的时候才会创建它们。

    /* 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;

每个线程的变量不是存放在ThreadLocal类中,都是存放在当前线程的变量之中,也就是说存放在当前线程的上下文空间中,其线程本身相当于变量的一个承载工具,通过set方法将变量添加到线程的threadLocals变量中,通过get方法能够从它的threadLocals变量中获取变量。如果线程一直不终止,那么这个本地变量将会一直存放在它的threadLocals变量中,所以可以通过remove方法删除本地变量。
set方法

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
          //以当前线程为参数,查找线程的变量threadLocals(1)
        ThreadLocalMap map = getMap(t);
        //如果线程变量map不为null,直接添加本地变量,
          //key为当前定义的ThreadLocal变量的this引用,值为添加到本地的变量值
        if (map != null) {
            map.set(this, value);
        } else {
           //如果为null,这创建一个ThreadLocalMap对象赋值给当前线程t(2)
            createMap(t, value);
        }
    }

上述代码(1)处获取当前线程的threadLocals变量,方法如下:

//获取线程的threadLocals变量,并将本地变量和ThreadLocal对象引用绑定到变量上    
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

上述代码(2)处创建ThreadLocalMap对象并初始化当前线程的threadLocals变量,方法如下:

    void createMap(Thread t, T firstValue) {
       //this是当前ThreadLocal对象的引用,firstValue是变量值
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • get方法

    public T get() {
       //获取当前线程|调用者的线程(1)
        Thread t = Thread.currentThread();
       //获取当前线程的threadLocals变量(2)
        ThreadLocalMap map = getMap(t);
       //如果ThreadLocalMap变量不为null,就可以在map中查找到本地变量的值(3)
        if (map != null) {
            //通过当前ThreadLocal对象的this引用获取变量中
            ThreadLocalMap.Entry e = map.getEntry(this);
          // 如果变量值不为空,则转换为指定的类型返回
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
      //threadLocals变量为null,则对当前线程的threadLocals的变量进行初始化
        return setInitialValue();
    }

    private T setInitialValue() {
        //调用抽象初始化方法,默认:null
        T value = initialValue();
      //获取当前线程
        Thread t = Thread.currentThread();
       //查找当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果map不为null,则直接添加本地变量,key为当前ThreadLocal的this引用,value为本地变量
        if (map != null) {
            map.set(this, value);
        } else {
            //首次添加,创建对应的threadLocals变量
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
  • remove方法的实现
     public void remove() {
        //获取当前线程的threadLocals变量,如果变量非null,则删除当前ThreadLocal引用对应的变量值
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             //删除ThreadLocalMap中存储的数据(1)
             m.remove(this);
         }
     }

上述(1)中的remove方法是ThreadLocal.ThreadLocalMap类的方法:

        private void remove(ThreadLocal<?> key) {
            //当前ThreadLocal变量所在的table数组位置
            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)]) {
                if (e.get() == key) {
                  //调用WeakReference的clear方法清除对ThreadLocal的弱引用
                    e.clear();
                  //清理key为null的元素
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

四、ThreadLocal不支持继承性
​ 在同一个ThreadLocal变量在父线程中被设置值之后,在子线程中是获取不到的(由二中的实例输出可以看出),threadLocals中为当前调用线程的本地变量,所以子线程是无法获取父线程的变量的;一开始我们介绍的时候说Thread类中还有一个inheritableThreadLocals变量,其值是存储的子线程的变量,所以可以通过InheritableThreadLocal类获取父线程的变量;

五、InheritableThreadLocal类源码简介
InheritableThreadLocal类是ThreadLocal类的子类,重写了chidValue、getMap、createMap三个方法,其中createMap方法在被调用的时候创建的是inheritableThreadLocals变量值(ThreadLocal类中创建的是threadLocals变量的值),getMap方法在被get或set方法调用的时候返回的也是线程的inheritableThreadLocals变量。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

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

那chidValue方法又是在哪里被调用呢?它会在ThreadLocalMap的中被调用:

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                       //调用重写的childValue方法
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

子线程是如何继承父线程的变量的,看Thread类的如下代码:

    private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
                //获取当前线程(父线程)
        Thread parent = currentThread();
        //安全校验
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security manager doesn't have a strong opinion
               on the matter, use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(
                        SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;//设置为当前线程组
        this.daemon = parent.isDaemon();//判定是否是守护线程同父线程
        this.priority = parent.getPriority();//优先级同父线程
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        //如果是否初始化线程的inheritThreadLocals变量为true并且父线程的inheritThreadLocals变量不为null
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
           //设置子线程的inheritThreadLocals变量为父线程的inheritThreadLocals变量
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        this.tid = nextThreadID();
    }

InheritableThreadLocal类通过重写方法在调用get、set方法的时候会调用重写的方法,调用方法时会对线程的

inheritableThreadLocals变量进行初始化,在对子线程进行初始化的时候会将子线程的

inheritableThreadLocals变量赋值为父线程的inheritableThreadLocals变量值,这样就实现了子线程继承父线程问题。

六、拓展ThreadLocal使用不当引起的内存泄漏问题
首先了解下什么是弱引用,弱引用也就是GC会主动的帮你把内存回收掉,但是当弱引用指定的对象有强引用指向时GC则不会回收对象。

ThreadLocalMap的内部实现实际上是一个Entry集合,Entry类继承了WeakReference类,表示其是一个弱引用对象,其构造函数super调用了父类的构造函数,传递了k变量,即ThreadLocal对象的引用,也就是说Map集合中存储的是ThreadLocal的弱引用。

    static class ThreadLocalMap {

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

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

如果当前线程一直存在且没有调用ThreadLocal的remove方法,那么ThreadLocal会在下次GC的时候将弱引用对象回收,这样就会造成ThreadLocalMap对象中的Entry对象的key为null,但是value值还是存在强引用关系,这样就会造成内存泄漏问题;引用链关系如下:

Thread->ThreadLocalMap->Entry->value

弱引用会在没有强引用的情况下在下次GC的时候自动的回收掉,每次使用完后要记得主动的调用remove方法,而且ThreadLocal每次调用get、set、remove的时候都会直接或者间接的调用expungeStaleEntry方法清除掉key为null的Entry,从而避免了内存泄漏。
七、拓展InheritableThreadLocal类子线程使用线程池更改存储变量值不变问题

 

public class Test {
    public static final ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    private static ExecutorService executorService = Executors.newFixedThreadPool(1);

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InterruptedException {
        threadLocal.set("主线程1。。。");
        System.out.println("线程1:" + threadLocal.get());
        executorService.submit(TtlRunnable.get(() -> System.out.println("子线程:" + Thread.currentThread().getName() + ":" + threadLocal.get())));
        Thread.sleep(1000);
        threadLocal.set("主线程2。。。");
        System.out.println("线程2:" + threadLocal.get());
        executorService.submit(TtlRunnable.get(() -> System.out.println("子线程:" + Thread.currentThread().getName() + ":" + threadLocal.get())));
    }

}

 

输出结果:

线程1:主线程1。。。
子线程:pool-1-thread-1:主线程1。。。
线程2:主线程2。。。
子线程:pool-1-thread-1:主线程1。。。

可以看到在运行线程2之前会修改InheritableThreadLocal内部存的值,但是在线程池内部获取值还是原来的,这其实又涉及到了线程池等会池化复用线程情况下,提供ThreadLocal值传递问题,推荐使用阿里开源的TTLhttps://github.com/alibaba/transmittable-thread-local

注意项
1.ThreadLocal与当前线程绑定,如果不用的时候需及时进行remove,否则会导致内存泄露。
2.InheritableThreadLocal 中是将父线程中的 inheritableThreadLocals 浅拷贝到到子线程中,代码上看就算不使用也会拷贝。(如理解有误请指正)
3.父子线程的 inheritableThreadLocals 并不共享,如果你在父线程中执行了remove,子线程中不会受影响,依旧可以get出来。如果有使用到判空get值做判空处理,需注意。
4.inheritableThreadLocals 只会在初始化时进行拷贝,如果使用线程池需要注意。可看下面例子。

注意 使用静态和不使用静态时候

  使用静态的 InheritableThreadLocal 线程池复用时候不会有问题

  ThreadLocal是线程本地变量,每个线程有自己的副本;而InheritableThreadLocal具有继承性,在创建子线程时,子线程可以继承父线程的变量副本。

private static final InheritableThreadLocal<T> t1 = new InheritableThreadLocal<T>();
        ThreadLocal<String> t = new InheritableThreadLocal<>();
        t.set("test");
 
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            System.out.println(t.get());
            t.remove();
        });
        // 确保上面代码执行完毕
        Thread.sleep(1000);
 
        // 再次执行,在同一线程中,没有新建线程,所以不会进行重新拷贝,输出为null
        executorService.execute(() -> {
            System.out.println(t.get());
        });

 

 



标签:变量,Thread,ThreadLocalMap,数据共享,ThreadLocal,线程,InheritableThreadLocal,null
From: https://www.cnblogs.com/shanheyongmu/p/17922183.html

相关文章

  • ThreadLocal的内存泄露?什么原因?如何避免?
    前言在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮性。内存泄露内存泄露为程序在申请内存后,无法释放已申请的内存空间,一......
  • ThreadLocal阅读
    ThreadLocal阅读目录ThreadLocal阅读ThreadLocalMap细究ThreadLocalMapgetEntrysetremoveThreadLocalMap其他的细节ThreadLocalSuppliedThreadLocalTerminatingThreadLocalInheritableThreadLocal首先基于ThreadLocal的简单使用场景,梳理一下下面三个的类之间的关系:ThreadThre......
  • 十二、路由参数和应用数据共享
    数据传递页面跳转,使用路由传递。//page1跳转传递参数router.pushUrl({url:'pages/ParamRouter2',params:{name:'HarmonyOS4.0',age:20}})//page2接收参数le......
  • 对ThreadLocal的理解
    1.ThreadLocal概述ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal同时实现了线程内的资源共享案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每......
  • ThreadLocal
    publicclassUserContext{privatestaticThreadLocal<User>userThreadLocal=newThreadLocal<>();publicstaticvoidsetUser(Useruser){userThreadLocal.set(user);}publicstaticUsergetUser(){......
  • ThreadLocal原理
    ThreadLocal主要起到线程隔离作用,使得每个线程拥有自己独立的一份数据,经过threadLocal处理的数据是线程独享的,不与其它线程分享或者干扰,因此能起到线程之间数据隔离的作用。ThreadLocal的几个核心方法:方法声明描述publicvoidset(Tvalue)设置当前线程绑定的局部变量pu......
  • Netty源码学习8——从ThreadLocal到FastThreadLocal(如何让FastThreadLocal内存泄漏do
    系列文章目录和关于我一丶引入在前面的netty源码学习中经常看到FastThreadLocal的身影,这一篇我们将从ThreadLocal说起,来学习FastThreadLocal的设计(《ThreadLocal源码学习笔记》)二丶从ThreadLocal说起ThreadLocal是JDK中实现线程隔离的一个工具类。实现线程隔离maybe你第一反应......
  • ThreadLocal的深度解读
    原文链接:https://zhuanlan.zhihu.com/p/624851777一、J2SE的原始描述Thisclassprovidesthread-localvariables.Thesevariablesdifferfromtheirnormalcounterpartsinthateachthreadthataccessesone(viaitsgetorsetmethod)hasitsown,independentlyin......
  • vue中组件之间数据共享
    1、父组件向子组件共享数据 2、子组件向父组件共享数据 3、兄弟组件之间的数据共享 ......
  • std::future与std::promise在C++多线程同步与数据共享中的应用
    1、std::promise与std::futurestd::promise与std::future通过配合使用完成数据的同步与共享,两者均是模板类;std::promise存储异步执行的值或异常;std::future提供可供访问的异步执行结果。二者配合使用伪码如下:std::promise<Type>pr;std::future<Type>fu(pr.get_fu......