• 【Java核心知识】ThreadLocal相关知识


    ThreadLocal

    什么是ThreadLocal

    ThreadLoacal类可以为每个线程保存一份独有的变量,该变量对于每个线程都是独占的。实现原理为每个Thread类中包含一个ThreadHashMapkey为变量的对应的ThreadLocal对象value为变量的

    在日常使用中,我们可以通过set(value)方法设置值,然后通过get()方法获取值,示例代码如下:

    我们通过线程池执行了五个线程,每个线程都有一份自己独有的id变量,实现了线程间的数据隔离。

    @Slf4j
    public class ThreadLocalTest {
        // ThreadLocal 变量,每个线程都有自己的副本
        private static final ThreadLocal<Integer> id = new ThreadLocal<>();
    
        @Test
        public void testThreadLocal() {
            // 创建线程池
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            try {
                for (int i = 0; i < 5; i ++) {
                    int finalI = i;
                    executorService.execute(() -> {
                        // 设置ThreadLocal 变量    
                        id.set(finalI);
                        // 获取ThreadLocal变量
                        log.info("The id of thread {} is {}", Thread.currentThread().getName(), id.get());
                    });
                }
            } finally {
                id.remove();
                executorService.shutdown();
            }
        }
    }
    
    • 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

    传统的HashMap是链地址法,发生碰撞放入桶内;ThreadLocalMap是开放地址法,发生冲突放到后一位

    实现原理

    那如何实现这种线程间的数据隔离呢?一种容易想到的方法是在Map中进行存储,每个ThreadLocal变量内维护一个Map,其中key为每个线程的IDvalue为对应的值。这样当调用get()方法时,会通过hash算法找到对应线程的值。

    jdk也确实是这么做的,只不过存放元素Map的实现不是HashMap,而是ThreadLocalMap,解决冲突的做法是开放寻址法,即出现冲突就往下一个临近位置找,直到找到空位置。

    早期的实现中,多个线程会共享一个ThreadLocalMap,这样随着线程数的增加,map就需要扩容,会消耗较多的资源;

    后来,每个线程都有自己的一个ThreadLocalMap,各自保存自己的局部变量,key为变量名,value为变量值。

    因为局部变量很少,所以一般不需要扩容。

    内存泄漏

    下面这张图是ThreadLocal对应的引用关系,可以看到,在新的设计下,ThreadLocalMap存在于Thread中,是和Thread的生命周期一致的。

    在这里插入图片描述

    ThreadLocalMapkeyThreadLocal对象,value是对应的值,需要注意的是ThreadHashMapkey会使用弱引用,这主要是为了当ThreadLocalnull后,除了ThreadHashMap持有的弱引用外,没有别的强引用,ThreadLocal对象可以被GC回收

    但是这样就导致ThreadHashMap中对应的keynull,其value引用的对象不会被GC回收,从而出现了内存泄漏。

    所以在使用完ThreadLocal变量后,尽量使用remove()方法进行清理,这样会把keynull的键值对删除。

    再回到上面的示例,上面的示例我们使用final修饰了ThreadLocal,保证其不会被修改,这样做会导致ThreadLocal永远存在强引用,不会被释放,容易发生内存泄露,所以需要我们显示调用remove()方法进行清理。

    参考链接

    ThreadLocal的内存泄露?什么原因?如何避免?

  • 相关阅读:
    PAT1021 个位数统计
    iOS开发之打包上传到App Store——(一)各种证书的理解
    WebSocke(简略笔记)
    R语言处理数量很大(千万级及以上)的数据时的拆分策略-案例一
    前端面试题2022-HTML篇
    机器学习(一):概述
    【Java 进阶篇】深入了解 Bootstrap 按钮和图标
    【教3妹学算法-每日1题】使数组中所有元素都等于零
    python pip3 安装psycopg2报错
    透过inode来理解硬链接和软链接
  • 原文地址:https://blog.csdn.net/qq_43705697/article/details/132642834