• ThreadLocal简解


    ThreadLocal特点

    ThreadLocal实现了线程间数据隔离,ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。简单来说就是一个公共的Map,map的key是Thread本身,value是线程携带的数据。

    ThreadLocal的简单使用

    使用方式一

    开启三个新的线程,每个线程对数据进行累加。

    public class TestThreadLocal {
    
        //线程本地存储变量
        private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
    
        public static void main(String[] args) {
            for (int i = 0; i < 3; i++) {//启动三个线程
                Thread t = new Thread() {
                    @Override
                    public void run() {
                        add10ByThreadLocal();
                    }
                };
                t.start();
            }
        }
    
        /**
         * 线程本地存储变量加 5
         */
        private static void add10ByThreadLocal() {
            for (int i = 0; i < 5; i++) {
                Integer n = THREAD_LOCAL_NUM.get();
                n += 1;
                THREAD_LOCAL_NUM.set(n);
                System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
            }
        }
    
    }
    

    执行结果

    Thread-1 : ThreadLocal num=1
    Thread-1 : ThreadLocal num=2
    Thread-2 : ThreadLocal num=1
    Thread-2 : ThreadLocal num=2
    Thread-2 : ThreadLocal num=3
    Thread-0 : ThreadLocal num=1
    Thread-2 : ThreadLocal num=4
    Thread-1 : ThreadLocal num=3
    Thread-2 : ThreadLocal num=5
    Thread-0 : ThreadLocal num=2
    Thread-1 : ThreadLocal num=4
    Thread-0 : ThreadLocal num=3
    Thread-1 : ThreadLocal num=5
    Thread-0 : ThreadLocal num=4
    Thread-0 : ThreadLocal num=5
    

    每个线程的值都是1~5,没有出现混加。这就实现了每个线程之间的数据的隔离。

    使用方式二

    开启一个定长为3的线程池,每个线程对数据进行累加。

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TestThreadLocal2 {
    
        //线程本地存储变量
        private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
    
        public static void main(String[] args) {
            ExecutorService cachedThreadPool = Executors.newFixedThreadPool(3);
            for (int i = 0; i < 10; i++) {
                cachedThreadPool.execute(() -> add10ByThreadLocal());
            }
        }
    
        /**
         * 线程本地存储变量加 5
         */
        private static void add10ByThreadLocal() {
            for (int i = 0; i < 5; i++) {
                Integer n = THREAD_LOCAL_NUM.get();
                n += 1;
                THREAD_LOCAL_NUM.set(n);
                System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
            }
        }
    
    }
    

    执行结果

    pool-1-thread-1 : ThreadLocal num=1
    pool-1-thread-3 : ThreadLocal num=1
    pool-1-thread-1 : ThreadLocal num=2
    pool-1-thread-2 : ThreadLocal num=1
    pool-1-thread-1 : ThreadLocal num=3
    pool-1-thread-3 : ThreadLocal num=2
    pool-1-thread-1 : ThreadLocal num=4
    pool-1-thread-2 : ThreadLocal num=2
    pool-1-thread-1 : ThreadLocal num=5
    pool-1-thread-3 : ThreadLocal num=3
    pool-1-thread-2 : ThreadLocal num=3
    pool-1-thread-1 : ThreadLocal num=6
    pool-1-thread-3 : ThreadLocal num=4
    pool-1-thread-1 : ThreadLocal num=7
    pool-1-thread-2 : ThreadLocal num=4
    pool-1-thread-1 : ThreadLocal num=8
    pool-1-thread-3 : ThreadLocal num=5
    pool-1-thread-1 : ThreadLocal num=9
    pool-1-thread-2 : ThreadLocal num=5
    pool-1-thread-1 : ThreadLocal num=10
    pool-1-thread-3 : ThreadLocal num=6
    pool-1-thread-1 : ThreadLocal num=11
    pool-1-thread-2 : ThreadLocal num=6
    pool-1-thread-2 : ThreadLocal num=7
    pool-1-thread-1 : ThreadLocal num=12
    pool-1-thread-3 : ThreadLocal num=7
    pool-1-thread-3 : ThreadLocal num=8
    pool-1-thread-3 : ThreadLocal num=9
    pool-1-thread-3 : ThreadLocal num=10
    pool-1-thread-1 : ThreadLocal num=13
    pool-1-thread-2 : ThreadLocal num=8
    pool-1-thread-1 : ThreadLocal num=14
    pool-1-thread-3 : ThreadLocal num=11
    pool-1-thread-1 : ThreadLocal num=15
    pool-1-thread-2 : ThreadLocal num=9
    pool-1-thread-1 : ThreadLocal num=16
    pool-1-thread-3 : ThreadLocal num=12
    pool-1-thread-1 : ThreadLocal num=17
    pool-1-thread-2 : ThreadLocal num=10
    pool-1-thread-1 : ThreadLocal num=18
    pool-1-thread-3 : ThreadLocal num=13
    pool-1-thread-1 : ThreadLocal num=19
    pool-1-thread-2 : ThreadLocal num=11
    pool-1-thread-1 : ThreadLocal num=20
    pool-1-thread-3 : ThreadLocal num=14
    pool-1-thread-3 : ThreadLocal num=15
    pool-1-thread-2 : ThreadLocal num=12
    pool-1-thread-2 : ThreadLocal num=13
    pool-1-thread-2 : ThreadLocal num=14
    pool-1-thread-2 : ThreadLocal num=15
    

    问题就出现了,由于线程池的线程是可以重复使用的,所以就出现了数据错乱的现象。所以在合线程池结合使用时,需要注意及时清理线程的数据。

    ThreadLocal方法简介

    主要方法如下

    public T get() { }
    public void set(T value) { }
    public void remove() { }
    protected T initialValue() { }
    
    • get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
    • set()用来设置当前线程中变量的副本
    • remove()用来移除当前线程中变量的副本
    • initialValue()是一个protected方法,一般是用来在使用时进行重写的,如果在没有set的时候就调用get,会调用initialValue方法初始化内容。

    源码分析

    set
         public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    set方法会获取当前的线程,通过当前线程获取ThreadLocalMap对象。然后把需要存储的值放到这个map里面。如果没有就调用createMap创建对象。

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

    getMap方法直接返回当前Thread的threadLocals变量,这样说明了之所以说ThreadLocal是线程局部变量就是因为它只是通过ThreadLocal把变量存在了Thread本身而已。

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

    在set的时候如果不存在threadLocals,直接创建对象。由上看出,放入map的key是当前的ThreadLocal,value是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个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();
        }
    

    get方法就比较简单,获取当前线程,尝试获取当前线程里面的threadLocals,如果没有获取到就调用setInitialValue方法,setInitialValue基本和set是一样的,就不累累述了。

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

    使用场景

    1. 比如线程中处理一个非常复杂的业务,可能方法有很多,那么,使用 ThreadLocal 可以代替一些参数的显式传递;
    2. 比如用来存储用户 Session。Session 的特性很适合 ThreadLocal ,因为 Session 之前当前会话周期内有效,会话结束便销毁。
    3. 在一些多线程的情况下,如果用线程同步的方式,当并发比较高的时候会影响性能,可以改为 ThreadLocal 的方式,例如高性能序列化框架 Kyro 就要用 ThreadLocal 来保证高性能和线程安全;
    4. 还有像线程内上线文管理器、数据库连接等可以用到 ThreadLocal;

    附加

    ThreadLocalMap中使用key为ThreadLocal的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。

    所以如果 ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap 中使用这个ThreadLocal的key也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

    ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

  • 相关阅读:
    Redis详解(三)- redis的六大数据类型详细用法
    Redis详解(二)- redis的配置文件介绍
    《OR Talk NO.11 | 清能互联赖晓文:电力系统中的运筹优化应用》
    《OR Talk NO.10 | 浙江大学徐金明:分布式优化算法何时能在性能上与集中式算法相媲美?》
    《OR Talk NO.9 | 深圳大学龚元浩:比几何流快一万倍的曲率滤波算法》
    《OR Talk NO.4 | Attain.ai 创始人李玉喜:强化学习遇见组合优化》
    《OR Talk NO.3 | 滴滴 AI Labs 秦志伟:深度强化学习在网约che交易市场中的应用》
    《OR Talk NO.2 | 胡武华博士:运筹优化理论在物流行业中的应用实践》
    《KDD 2018 | Conventional Tutorials》
    《OR Talk NO.13 | MIT 博士与你分享电商供应链算法实战心得》
  • 原文地址:https://www.cnblogs.com/chenglc/p/11911287.html
Copyright © 2020-2023  润新知