Java中ThreadLocal线程变量的实现原理

 更新时间:2022年06月26日 11:17:20   作者:羡羡ˇ  
本文主要介绍了Java中ThreadLocal线程变量的实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

ThreadLocal是什么?

ThreadLocal 使得我们可以创建线程私有的变量, 这个变量相对于其他线程来说是不可见的,ThreadLocal为变量在每个线程中都创建了一个副本 , 每个线程可以访问自己私有的线程变量,代码示例如下 : 

public class ThreadLocalDemo {
 
    //创建一个ThreadLocal对象,用来为每个线程会复制保存一份变量,实现线程封闭
    private  static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };
 
    public static void main(String[] args) {
          //线程0
          new Thread(){
              @Override
              public void run() {
                   localNum.set(1);
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  localNum.set(localNum.get()+10);
                  System.out.println(Thread.currentThread().getName()+":"+localNum.get());//11
              }
          }.start();
        //线程1
        new Thread(){
            @Override
            public void run() {
                localNum.set(3);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                localNum.set(localNum.get()+20);
                System.out.println(Thread.currentThread().getName()+":"+localNum.get());//23
            }
        }.start();
        
        System.out.println(Thread.currentThread().getName()+":"+localNum.get());//0
    }
}

如上所述, 算上main线程与新建的两个线程 ,总共三个线程 , 每个线程都包含自己的私有变量,此处我们设置值1 , set() 和 get() 方法用来设置值和获得值, 执行结果如下 : 

ThreadLocal实现原理分析

ThreadLocal是一个泛型类 , 可以接受任何类型的对象 , 其内部维护了一个ThreadLocalMap 的静态内部类,  我们使用的 get(), set()等其实都来自这个类, 每次都会为当前线程创建一个ThreadLocalMap对象, 用来记录私有的值

先看 set() 方法

public void set(T value) {
    //拿到当前线程
    Thread t = Thread.currentThread();
    //拿到当前线程map
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //存在设置值
        map.set(this, value);
    else
        //不存在则创建
        createMap(t, value); 
}
void createMap(Thread t, T firstValue) {
    //threadLocals属性即为此map
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

接着是get() 方法

public T get() {
    //拿到当前线程
    Thread t = Thread.currentThread();
    //拿到当前线程对应的map
    ThreadLocalMap map = getMap(t);
    //如果已有map
    if (map != null) {
        //取值操作, 拿到对应的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
           @SuppressWarnings("unchecked")
           T result = (T)e.value;
           return result;
        }
    }
    //没有map, 则去创建初始化一个map
    return setInitialValue();
}
private T setInitialValue() {
    //initialValue()方法返回的value为null
    T value = initialValue();
    //拿到当前线程去创建对应的map
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocal可以理解为对ThreadLocalMap的封装

ThreadLocal内存泄漏问题

在ThreadLocalMap中 , 使用 ThreadLocal 的弱引用作为 key 

这样的话, 如果一个ThreadLocal不存在外部强引用时, 那么key注定要被GC回收 , 这样导致ThreadLocalMap 中key为null , 而value还存在着强引用链

一个线程可以同时拥有多个ThreadLocal, 如果作为弱引用的key被回收后, value还不能被回收,那么这就导致此ThreadLocal的生命周期和此线程是一样长的(因为线程执行完毕后此value的强引用链才会断), 如果线程一直不结束, 堆积的value也一直无法被回收, 那么就会产生内存泄漏问题

这里解决问题的方式是 : 每次使用完ThreadLocal后都调用它的remove()方法清除数据

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

这里我们再来看一下key作为强弱引用的区别

如果key作为强引用, 那么它的生命周期和线程一样长,存在稳定的强引用链,无法被回收,产生内存泄漏问题, 而如果作为弱引用, GC则会自动的去回收它们, 在后续的remove()方法中也可以更好的去回收value , 所以我们一般将ThreadLocal设计成 private static 的, 在使用完后用remove()方法去手动删除它们

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

相关文章

  • 解决nacos升级spring cloud 2020.0无法使用bootstrap.yml的问题

    解决nacos升级spring cloud 2020.0无法使用bootstrap.yml的问题

    这篇文章主要介绍了解决nacos升级spring cloud 2020.0无法使用bootstrap.yml的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 聊聊Mybatis中sql语句不等于的表示

    聊聊Mybatis中sql语句不等于的表示

    这篇文章主要介绍了Mybatis中sql语句不等于的表示方式,具有很好的参考价值,希望对大家有所帮助。
    2021-07-07
  • 详解Java事件编程的使用

    详解Java事件编程的使用

    Java事件在很多地方都可以使用,合理的使用事件编程,相比常规逻辑的编程,这可达到主次分明,让程序吞吐量即处理能力更强,改动更少,下面我们举一个例子说明如何使用Java使用,需要的朋友可以参考下
    2021-06-06
  • JAVA 实现二叉树(链式存储结构)

    JAVA 实现二叉树(链式存储结构)

    本篇文章主要介绍用JAVA 实现二叉树,并提供实例.对二叉树数据结构很好的学习实践,有需要的朋友可以参考下
    2016-07-07
  • datatables 带查询条件java服务端分页处理实例

    datatables 带查询条件java服务端分页处理实例

    本篇文章主要介绍了datatables 带查询条件java服务端分页处理实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java中替代equals,compareTo和toString的方法

    Java中替代equals,compareTo和toString的方法

    这篇文章主要介绍了Java中替代equals,compareTo和toString的方法,文中代码十分详细,帮助大家更好的理解的学习,感兴趣的朋友可以了解下
    2020-06-06
  • SpringMVC通过RESTful结构实现页面数据交互

    SpringMVC通过RESTful结构实现页面数据交互

    RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义。RESTFUL适用于移动互联网厂商作为业务接口的场景,实现第三方OTT调用移动网络资源的功能,动作类型为新增、变更、删除所调用资源
    2022-08-08
  • Mybatis批量插入数据返回主键的实现

    Mybatis批量插入数据返回主键的实现

    这篇文章主要介绍了Mybatis批量插入数据返回主键的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • SpringBoot服务访问路径动态处理方式

    SpringBoot服务访问路径动态处理方式

    这篇文章主要介绍了SpringBoot服务访问路径动态处理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • System.currentTimeMillis()计算方式与时间的单位转换详解

    System.currentTimeMillis()计算方式与时间的单位转换详解

    这篇文章主要介绍了System.currentTimeMillis()计算方式与时间的单位转换详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05

最新评论