• 掌握ThreadLocal的王者段位


    ThreadLocal概述

    ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。

    ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。

    注意:
    但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

    使用案例

    笔者以汽车领域为案例,请求的处理会话线程,持有当前车机系统的信息:系统名称、版本,作为ThreadLocal的变量内容,实际的应用中通常将操作该变量的操作单独封装成一个类。

    领域实体

    package com.linfanchen.springboot.lab.tr;
    
    class OsBO {
        /**
         * 车机系统名称
         */
        private String osName;
    
        /**
         * 车机系统版本
         */
        private Integer version;
    
        public String getOsName() {
            return osName;
        }
    
        public void setOsName(String osName) {
            this.osName = osName;
        }
    
        public Integer getVersion() {
            return version;
        }
    
        public void setVersion(Integer version) {
            this.version = version;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    ThreadLocal操作类

    package com.linfanchen.springboot.lab.tr;
    
    import java.util.HashMap;
    
    public class LocalOS {
        /**
         * 线程持有的变量
         */
        public static final ThreadLocal<HashMap<String, OsBO>> localOS = new ThreadLocal<>();
    
        /**
         * 展示车机系统
         */
        static void showOs(String carName) {
            // 输出本地变量
            System.out.println(carName + " 的车机系统名称:" + localOS.get().get("os").getOsName() + ", 版本:" + localOS.get().get("os").getVersion());
    
            // 清除本地变量的值,防止内存泄漏
            localOS.remove();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    使用ThreadLocal变量

    package com.linfanchen.springboot.lab.tr;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.HashMap;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ThreadLocalTest {
    
    
    
    
        @Test
        public void firstTest() throws InterruptedException {
    
            /**
             * 创建线程,模拟实际场景中处理请求会话的线程
             */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    HashMap<String, OsBO> localOsValue = new HashMap<>();
                    OsBO osBO = new OsBO();
                    osBO.setOsName("IDrive");
                    osBO.setVersion(7);
                    localOsValue.put("os", osBO);
    
                    LocalOS.localOS.set(localOsValue);
    
                    // 获取当前线程的操作系统
                    LocalOS.showOs("BMW");
    
                    // 删除后再打印
                    System.out.println("BMW 线程下删除后再打印:" + LocalOS.localOS.get());
    
                }
            }, "BMW").start();
    
    
            /**
             * 创建线程,模拟实际场景中处理请求会话的线程
             */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    HashMap<String, OsBO> localOsValue = new HashMap<>();
                    OsBO osBO = new OsBO();
                    osBO.setOsName("MBUS");
                    osBO.setVersion(1);
                    localOsValue.put("os", osBO);
    
                    LocalOS.localOS.set(localOsValue);
    
                    // 获取当前线程的操作系统
                    LocalOS.showOs("Benz");
    
                    // 删除后再打印
                    System.out.println("Benz 线程下删除后再打印:" + LocalOS.localOS.get());
    
                }
            }, "Benz").start();
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    输出

    BMW 的车机系统名称:IDrive, 版本:7
    Benz 的车机系统名称:MBUS, 版本:1
    BMW 线程下删除后再打印:null
    Benz 线程下删除后再打印:null
    
    • 1
    • 2
    • 3
    • 4

    原理解析

    常用方法

    方法作用
    ThreadLocal()创建ThreadLocal对象
    public void set( T value)设置当前线程绑定的局部变量
    public T get()获取当前线程绑定的局部变量
    public T remove()移除当前线程绑定的局部变量,该方法可以帮助JVM进行GC
    protected T initialValue()返回该线程局部变量的初始值

    在这里插入图片描述

    • 每个Thread维护一个ThreadLocalMap,这个ThreadLocalMap的key是ThreadLocal实例本身,value才是真正要存储的值Object。
    • 每个Thread线程内部都有一个ThreadLocalMap。
    • Map里面存储ThreadLocal对象(key)和线程的变量副本(value)。
    • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
    • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

    这样设计的好处:

    • 减少ThreadLocalMap存储的Entry数量:因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。在实际运用当中,往往ThreadLocal的数量要少于Thread的数量。
    • 当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用(但是不能避免内存泄漏问题,解决内存泄漏问题应该在使用完后及时调用remove()对ThreadMap里的Entry对象进行移除,由于Entry继承了弱引用类,会在下次GC时被JVM回收)。

    ThreadLocal的set()方法

    设置当前线程绑定的局部变量

    
    public void set(T value) {
     		// 1.获取当前线程对象
            Thread t = Thread.currentThread();
            // 2.获取当前线程的threadLocals
            ThreadLocal.ThreadLocalMap map = this.getMap(t);
            if (map != null) { // 3.1如果 threadLocals 非空,添加 threadLocals 值,key为当前 ThreadLocal实例对象,value为传入的待设值
                map.set(this, value);
            } else { // 3.2如果 threadLocals 为空, 则进行创建初始化操作
                this.createMap(t, value);
            }
    }
    
    
    /**
    * 根据当前线程获取线程内部的Thread.threadLocals属性(类型为: ThreadLocalMap)
    * 
    * @param  t the current thread 当前线程
    * @return the map 对应维护的ThreadLocalMap 
    */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    /**
    * 创建初始化,key为当前 ThreadLocal 实例对象
    */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    get方法

    /**
    * 获取当前线程中保存ThreadLocal的值
    * 
    *	如果当前线程没有此ThreadLocal变量,则它会通过调用initialValue方法进行初始化值
    *
    */
    public T get() {
    		// 获取当前线程对象
            Thread t = Thread.currentThread();
            // 获取此线程对象中维护的ThreadLocalMap对象
            ThreadLocal.ThreadLocalMap map = this.getMap(t);
            if (map != null) {
            	// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
                ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                	// 获取存储实体 e 对应的 value值,即为我们想要的当前线程对应此ThreadLocal的值
                    T result = e.value;
                    return result;
                }
            }
    
            return this.setInitialValue();
        }
    
    /**
    * 初始化值
    */
    private T setInitialValue() {
            T value = this.initialValue();
            Thread t = Thread.currentThread();
            // 根据当前线程获取map
            ThreadLocal.ThreadLocalMap map = this.getMap(t);
            if (map != null) {
            	// 获取到了map,则设置对应的值
                map.set(this, value);
            } else {
            	// 没有获取到则创建map
                this.createMap(t, value);
            }
    
    		// 判断对象的类是否 TerminatingThreadLocal,如果是则注册上去
            if (this instanceof TerminatingThreadLocal) {
                TerminatingThreadLocal.register((TerminatingThreadLocal)this);
            }
    
            return value;
        }
    
    /**
    * 默认的初始值为null
    * 如果想ThreadLocal线程局部变量有一个除null以外的初始值,
    * 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
    * 通常, 可以通过匿名内部类的方式实现
    */
    protected T initialValue() {
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    执行流程:
    在这里插入图片描述

    remove方法

    /**
    * 删除当前线程中保存的ThreadLocal对应的实体entry
    * 
    */
    public void remove() {
    		// 1.获取当前线程对象中维护的ThreadLocalMap对象
            ThreadLocal.ThreadLocalMap m = this.getMap(Thread.currentThread());
            if (m != null) {
            	// 2.存在则调用map.remove
                // 以当前ThreadLocal为key删除对应的实体entry
                m.remove(this);
            }
    
        }
    
    /**
    * m.remove(this)为此方法
    */
    private void remove(ThreadLocal<?> key) {
                ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;
                int len = tab.length;
                int i = key.threadLocalHashCode & len - 1;
    			
    			// 遍历ThreadLocalMap
                for(ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                	// 比较每一个entry,找到相等的进行清理
                    if (e.get() == key) {
                        e.clear();
                        this.expungeStaleEntry(i);
                        return;
                    }
                }
    
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    理解ThreadLocalMap对象

    本质上来讲, 它就是一个Map, 但是这个ThreadLocalMap与我们平时见到的Map有点不一样:

    • 它没有实现Map接口;
    • 它没有public的方法, 最多有一个default的构造方法, 因为这个ThreadLocalMap的方法仅仅在ThreadLocal类中调用, 属于静态内部类;
    • ThreadLocalMap的Entry实现继承了WeakReference> ;
    • 该方法仅仅用了一个Entry数组来存储Key, Value; Entry并不是链表形式, 而是每个bucket里面仅仅放一个Entry;

    常见的坑点

    • 内存泄漏
    • hash冲突

    参考文档:
    https://blog.csdn.net/u010445301/article/details/111322569

    https://blog.csdn.net/silence_yb/article/details/124265702

    https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html

    https://zhuanlan.zhihu.com/p/34406557

    https://www.cnblogs.com/eric-fang/p/13680015.html

    https://blog.csdn.net/fascinate_/article/details/113524837

    https://zhuanlan.zhihu.com/p/346291694

    ThreadLocal详解

  • 相关阅读:
    css中filter属性设置后导致页面定位失效
    解密Docker容器网络
    token、cookie、session
    JUC并发编程学习总结
    DBNN实验进展
    spring本地事务失效的情况
    Using Set Processing Examples 使用集合处理示例
    【文生图系列】Stable Diffusion Webui安装部署过程中bug汇总(Linux系统)
    深入了解Java 8 新特性:lambda表达式进阶
    谣言检测论文精读——3.Detect Rumor and Stance Jointly by Neural Multi-task Learning
  • 原文地址:https://blog.csdn.net/oschina_41731918/article/details/126048741