Java线程中的ThreadLocal类解读

 更新时间:2023年11月28日 10:30:22   作者:后仰大风车  
这篇文章主要介绍了Java线程中的ThreadLocal类解读,ThreadLocal是一个泛型类,作用是实现线程隔离,ThreadLocal类型的变量,在每个线程中都会对应一个具体对象,对象类型需要在声明ThreadLocal变量时指定,需要的朋友可以参考下

Java的ThreadLocal类

ThreadLocal是一个泛型类,作用是实现线程隔离,ThreadLocal类型的变量,在每个线程中都会对应一个具体对象,对象类型需要在声明ThreadLocal变量时指定。

ThreadLocal的使用示例

package org.example.thread;

import org.example.domain.Book;

public class ThreadLocalTest {

    ThreadLocal<Book> localBook = new ThreadLocal<>();

    Book book = new Book();

    public void test() {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                localBook.set(book);
                System.out.println(localBook.get());
                localBook.remove();
            }
        });
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(localBook.get());

        localBook.remove();
    }

    public static void main(String[] arg) {
        ThreadLocalTest localBookTest = new ThreadLocalTest();
        localBookTest.test();
    }
}

运行结果如下:

在这里插入图片描述

示例代码中,只声明了一个 ThreadLocal 变量localBook,在子线程中设置了localBook的值,但是在最后主线程中进行打印时,发现为null,和子线程中的结果不一样。

具体原因还要看看ThreadLocal类,以及内部 set、get等方法的实现。

原理

1、ThreadLocal中有一个静态内部类 ThreadLocalMap,ThreadLocalMap中维护了一个 Entry数组,Entry又是ThreadLocalMap的内部类,用来表示一个KV键值对。部分源码如下:

    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;
            }
        }

        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table;

Entry的 key是一个指向ThreadLocal对象的弱引用,value则指向与key对应的Object对象。

2、Thread类中,有一个ThreadLocalMap类型的成员变量,初始为null:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

也就是说,每个线程对象中,都有一个ThreadLocalMap,它里面可以有很多个Entry,每个Entry中的key都指向一个ThreadLocal对象,value则指向与key相对应的Object对象。在线程中调用ThreadLocal对象的set、get方法,实际操作的是当前线程自己的ThreadLocalMap。

3、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);
        }
    }

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

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

果然,这里先拿到当前线程中的ThreadLocalMap,如果不为空则直接添加一组键值对,key是当前的ThreadLocal变量; 如果map为空,则新建一个map,同样添加一组键值对,key是当前的ThreadLocal变量,然后将map赋值给当前线程的ThreadLocalMap。

4、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方法拿到的也是线程本地的ThreadLocalMap中,与当前ThreadLocal变量对应的value对象。

5、remove方法

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

        private void remove(ThreadLocal<?> key) {
            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) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

remove方法,移除当前线程的ThreadLocalMap中,与当前ThreadLocal对象对应的键值对(key、value及Entry置空)。

内存泄漏相关

1、为什么ThreadLocalMap中的key要使用弱引用?

弱引用特性:在对象没有被强引用指向,而仅被弱引用指向的情况下,发生垃圾回收时,会直接清理掉该对象。 套用网上出现较多的一张图片来表示ThreadLocal变量的内存结构:

在这里插入图片描述

这里实线表示强引用,虚线表示弱引用。 假设key也是强引用,如果我们把 ThreadLocal Ref这个引用置为null,表示我们不再需要这个ThreadLocal对象,那么发生垃圾回收时,这个变量理应被回收掉;但实际并非如此,因为当前线程中还持有一个强引用,也就是ThreadLocalMap中的key还在指向它,那么只要线程不结束,这个没有实际作用的ThreadLocal对象就一直不会被回收,从而出现内存泄漏。而将key设计为弱引用,就能保证这种情况下ThreadLocal对象被回收,一定程度避免内存泄漏问题。

2、为什么使用完ThreadLocal对象,要在线程中调用remove方法? 虽然弱引用可以使不再使用的ThreadLocal对象被回收掉,但还有一个问题: Entry中的key被置为了null,对应的value已经无法通过key访问到,然而Current thread -> ThreadLocalMap -> Entry(value) -> my value 这条强引用链仍然存在,也就是说还存在key为null,但value不为null的entry,如果这类entry不被清理掉,还是会导致内存泄露。

key为null的entry如何清理? 在ThreadLocalMap的Entry数组中,key为null的entry也会占用一个数组下标,而Threadlocal的set、get等方法,正是根据key的hashCode计算得到数组下标,然后根据下标找到对应的entry进行操作。 为了维护ThreadLocalMap的可用性,不让这些key为null的无用entry占用过多空间,set、get方法在某些情况下也会对key为null的entry进行清理。但是用户有可能不再需要调用其他ThreadLocal变量的set或get方法,所以这种方式是被动且没有针对性的。

建议在不使用ThreadLocal对象之后,直接调用remove方法,其作用就是将对应entry的key、value置空,并将entry从数组中移除,切断下图中的这三处引用关系,防止内存泄漏。

在这里插入图片描述

到此这篇关于Java线程中的ThreadLocal类解读的文章就介绍到这了,更多相关Java的ThreadLocal类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • ThreadLocal线程在Java框架中的应用及原理深入理解

    ThreadLocal线程在Java框架中的应用及原理深入理解

    这篇文章主要介绍了ThreadLocal在Java框架中的应用及原理深入理解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • spring+netty服务器搭建的方法

    spring+netty服务器搭建的方法

    本篇文章主要介绍了spring+netty服务器搭建的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • spring cloud gateway集成hystrix实战篇

    spring cloud gateway集成hystrix实战篇

    这篇文章主要介绍了spring cloud gateway集成hystrix实战,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • ssm 使用token校验登录的实现

    ssm 使用token校验登录的实现

    这篇文章主要介绍了ssm 使用token校验登录的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • SpringBoot整合Redis的步骤

    SpringBoot整合Redis的步骤

    这篇文章主要介绍了SpringBoot整合Redis的步骤,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下
    2020-11-11
  • java利用phantomjs进行截图实例教程

    java利用phantomjs进行截图实例教程

    PlantomJs是一个基于javascript的webkit内核无头浏览器 也就是没有显示界面的浏览器,你可以在基于 webkit 浏览器做的事情,它都能做到。下面这篇文章主要给大家介绍了关于java利用phantomjs进行截图的相关资料,需要的朋友可以参考下
    2018-10-10
  • Java中的Set、List、Map的用法与区别介绍

    Java中的Set、List、Map的用法与区别介绍

    这篇文章主要介绍了Java中的Set、List、Map的用法与区别,需要的朋友可以参考下
    2016-06-06
  • Java实现的矩阵乘法示例

    Java实现的矩阵乘法示例

    这篇文章主要介绍了Java实现的矩阵乘法,简单描述了矩阵乘法的原理,并结合实例形式分析了java实现矩阵乘法的相关操作技巧,需要的朋友可以参考下
    2019-03-03
  • Java中JDBC的使用教程详解

    Java中JDBC的使用教程详解

    Java语言操作数据库 JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。本文讲解了JDBC的使用方法,需要的可以参考一下
    2022-06-06
  • 使用Nacos作为配置中心的命名空间、配置分组

    使用Nacos作为配置中心的命名空间、配置分组

    文章详细介绍了Spring Cloud Config配置中心的命名空间、配置集、配置集ID、配置分组以及如何在微服务中加载和使用这些配置,通过配置中心,可以实现配置隔离和集中管理,简化微服务的配置维护
    2024-12-12

最新评论