• 并发编程学习笔记(十、ThreadLocal)


    目录:

    • ThreadLocal介绍
    • ThreadLocal使用
    • ThreadLocal注意点

    ThreadLocal介绍

    ThreadLocal是线程的本地变量副本,它是每个线程独立维护的值,不受其它线程的影响。

    基本方法:

    • public void set(T value):设置当前局部变量的值。
    • public T get():获取对应线程局部变量的值。
    • public void remove():删除局部变量的值,目的是为了减少内存占用。因线程结束后将会被GC回收,所以此方法并不是必须调用,但它可以加快内存回收的速度。
    • public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier):创建一个线程本地变量副本。
    • protected T initialValue():为第一次访问变量提供默认值,返回值默认为null,一般会重写此方法。

    ThreadLocal使用

    public class ThreadLocalDemo {
        /**
         * 定义了一个ThreadLocal<Integer>对象,
         * 并重写它的initialValue方法,初始值是3
         * 这个对象会在三个线程间共享
         */
        private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 3);
        /**
         * 设置一个信号量,许可数为1,让三个线程顺序执行
         */
        private Semaphore semaphore = new Semaphore(1);
        /**
         * 一个随机数
         */
        private Random random = new Random();
    
        /**
         * 每个线程中调用这个对象的get方法,再调用一个set方法设置一个随机值
         */
        public class Worker implements Runnable {
            @Override
            public void run() {
                try {
                    // 随机延时1s以内的时间
                    Thread.sleep(random.nextInt(1000));
                    // 获取许可
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 从threadLocal中获取值
                int value = threadLocal.get();
                System.out.println(Thread.currentThread().getName() + " threadLocal old value : " + value);
                // 修改value值
                value = random.nextInt();
                // 新的value值放入threadLocal中
                threadLocal.set(value);
                System.out.println(Thread.currentThread().getName() + " threadLocal new value: " + value);
                System.out.println(Thread.currentThread().getName() + " threadLocal latest value : " + threadLocal.get());
                // 释放信号量
                semaphore.release();
                // 在线程池中,当线程退出之前一定要记得调用remove方法,因为在线程池中的线程对象是循环使用的
                threadLocal.remove();
            }
        }
    
        /**
         * 创建三个线程,每个线程都会对ThreadLocal对象进行操作
         */
        public static void main(String[] args) {
            ExecutorService es = Executors.newFixedThreadPool(3);
            ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
            es.execute(threadLocalDemo.new Worker());
            es.execute(threadLocalDemo.new Worker());
            es.execute(threadLocalDemo.new Worker());
            es.shutdown();
        }
    }

    你运行之后便可以看出,每个线程第一次调用TheadLocal对象的get方法时都得到初始值3,注意我们上面的代码是让三个线程顺序执行;

    显然从运行结果看,第一个线程结束后设置的新值,对第二个线程是没有影响的,第二个线程完成后设置的新值对第三个线程也没有影响。

    这就仿佛把ThreadLocal对象当做每个线程内部的对象一样,但实际上threadLocal对象是个外部类对象,内部类Worker访问到的是同一个threadLocal对象,也就是说是被各个线程共享的。

    这是如何做到的呢?我们就来看看ThreadLocal对象的内部原理。

    ThreadLocal实现原理

    1、ThreadLocal中的本地变量都会存储在ThreadLocalMap中,ThreadLocalMap是Thread的一个属性,这也就是它为什么不会受其它线程的影响的原因。

    2、ThreadLocalMap是key value结构,内部是Entry。

    你可以很容易的通过ThreadLocak的get、set函数证得:

     1 public T get() {
     2     // 拿到当前线程
     3     Thread t = Thread.currentThread();
     4     // 拿到当前线程的threadLocals
     5     ThreadLocalMap map = getMap(t);
     6     if (map != null) {
     7         // 获取ThreadLocals的key map
     8         ThreadLocalMap.Entry e = map.getEntry(this);
     9         if (e != null) {
    10             // 若有这个值,就返出去
    11             @SuppressWarnings("unchecked")
    12             T result = (T)e.value;
    13             return result;
    14         }
    15     }
    16     return setInitialValue();
    17 }    
    18 
    19 ThreadLocalMap getMap(Thread t) {
    20     return t.threadLocals;
    21 }    
    22 
    23 public void set(T value) {
    24     // 拿到当前线程
    25     Thread t = Thread.currentThread();
    26     // 拿到当前线程的threadLocals
    27     ThreadLocalMap map = getMap(t);
    28     if (map != null)
    29         // 若已经初始化了map,则填充值
    30         map.set(this, value);
    31     else
    32         // 若还未初始化map,则创建一个
    33         createMap(t, value);
    34 }

    ThreadLocal注意点

    1、内存泄漏:

    • 在线程池中,线程执行完后不被回收,而是返回线程池中。
    • Thread有个强引用指向ThreadLocalMapThreadLocalMap有强引用指向EntryEntry的key是弱引用的ThreadLocal对象
    • 如果ThreadLocal使用一次后就不在有任何引用指向它,JVM GC会将ThreadLocal对象回收掉,导致Entry变为{null: value}。
    • 此时这个Entry已经无效,因为key被回收了,而value无法被回收,一直存在内存中。

    解决方案:在执行了ThreadLocal.set()方法之后一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏。

  • 相关阅读:
    7.服务器上用命令ffprobe看流质量
    filebeat记录
    Dubbo 一篇文章就够了:从入门到实战
    c/c++系列的运算符优先级总结
    动态单链表的传统存储方式和10种常见操作-C语言实现
    代码的规范化—高质量程序的结构(一)
    动态分配的顺序线性表的十五种操作—C语言实现
    IOS开发初步
    大数据方面一个博客
    Task not found in root project
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/12514073.html
Copyright © 2020-2023  润新知