Java中ThreadLocal共享变量的使用

 更新时间:2024年01月02日 14:41:42   作者:EumenidesJ  
java.lang.ThreadLocal该类提供了线程局部变量,用于在当前线程中共享数据,本文主要介绍了Java中ThreadLocal共享变量的使用,具有一定的参考价值,感兴趣的可以了解一下

一、ThreadLocal

我们知道多线程访问同一个共享变量时,会出现线程安全问题,为了保证线程安全开发者需要对共享变量的访问操作进行适当的同步操作,如加锁等同步操作。

除此之外,Java提供了ThreadLocal类,当一个共享变量使用ThreadLocal声明时,它表明,当每个线程访问共享变量时,会把共享变量复制一份到线程的工作内存,之后线程对此共享变量进行操作时操作的都是线程工作内存的变量而不是主内存中的共享变量,从而不需要加锁的同步操作实现避免出现线程安全问题。

二、Thread使用代码示例

public class ThreadLocalTest {
    private static ThreadLocal<String> variable = new ThreadLocal<>(); // (1) 
 
    public static void main(String[] args) throws InterruptedException {
        variable.set(Thread.currentThread().getName());  // (2)
        // 创建线程一
        var thread1 = new Thread(() -> {
            System.err.println("Thread Name before set: " + Thread.currentThread().getName() + " " + variable.get());  // (3)
            variable.set(Thread.currentThread().getName()); // (4)
            System.err.println("Thread Name after set: " + Thread.currentThread().getName() + " " + variable.get()); // (5)
        });


        var thread2 = new Thread(() -> {
            System.err.println("Thread Name before set: " + Thread.currentThread().getName() + " " + variable.get()); // (6)
            variable.set(Thread.currentThread().getName()); // (7)
            System.err.println("Thread Name after set: " + Thread.currentThread().getName() + " " + variable.get()); // (8)
        });
        thread1.start();  // (9)
        thread2.start(); // (10)
        Thread.sleep(2000); // (11)
        System.err.println("main thread: " + variable.get()); // (12)
    }
}

输出:

Thread2 before set: Thread-1 null
Thread2 after set: Thread-1 Thread-1
Thread1 before set: Thread-0 null
Thread1 after set: Thread-0 Thread-0
main thread: main

示例中我们创建了两个线程,每个线程里都读取和设置全局的ThreadLcoal变量:

代码(1)创建了一个ThreadLocal共享变量variable,这里其实设置的是主线程工作内存里的共享变量副本

代码(2)主线程设置ThreadLocal变量variable

代码(3)线程一读取共享变量variable的值

代码(4)线程一设置共享变了variable的值,这里其实设置的是线程一工作内存里的共享变量副本

代码(5)线程一再次读取共享变量variable的值

代码(6)线程二读取共享变量variable的值

代码(7)线程二设置共享变了variable的值,这里其实设置的是线程二工作内存里的共享变量副本

代码(8)线程二再次读取共享变量variable的值

代码(9)启动线程一

代码(10)启动线程二

代码(11)主线程休眠2秒

代码(12)主线程读取共享变量variable的值

从输出我们可以看到,每个两个线程所操作的ThreadLocal变量互不影响,其实每个线程在设置和读取共享变量variable时操作的都是共享变量在线程自己工作内存里的副本,并不会影响到其他线程的值。

三、ThreadLocal原理

我们说线程操作ThreadLocal类型的变量时,会复制一个变量副本到线程工作空间,然后所有操作都是对副本变量进行的。那线程是怎么复制ThreadLocal变量到线程工作空间的,线程和ThreadLocal之前是怎么关联的。首先我们来看一看Thread的结构

Thread

可以看到Thread类有很多属性,我们现在只关心threadLocalsinheritableThreadLocals,这两个变量都是ThreadLocalMap类型的实例。TThreadLocalMap是一个ThreadLocal.ThreadLocalMap类型,这是一个特殊的Map。

首先看一下在前面的例子中我们是怎么在线程中使用ThreadLocal变量的,

variable.set(Thread.currentThread().getName()); // 设置ThreadLocal变量
variable.get();    // 读取ThreadLocal变量

接下来我们看看ThreadLocal变量的set和get方法。

ThreadLocal.get()相关源码如下:

public T get() {
  return get(Thread.currentThread()); // (1)
}

private T get(Thread t) {
  ThreadLocalMap map = getMap(t);  // (2)
  if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this); // (3)
    if (e != null) {
      @SuppressWarnings("unchecked")
      T result = (T) e.value;
      return result;
    }
  }
  return setInitialValue(t);   // (4)
}

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

从代码(1)可以看到,调用ThreadLocal的get()方法时,会将当前线程作为参数传递。代码(2)调用getMap方法获取ThreadLocalMap类型变量,如果map不为空则把ThreadLocal实例作为key获取值,这个值就是ThreadLocal变量的值(5)可以看到getMap方法返回的就是Thread类型的threadLocals变量。根据上述分析我们可以知道:

线程在读取ThreadLocal变量时,实际是获取当前线程的threadLocals变量,然后把ThreadLocal实例当做key从threadLocals查询对应的值。也就是说线程读取的ThreadLocal的实际值并不是存在ThreadLocal实例里的,而是存在线程的threadLocals里面,threadLocals是一个ThreadLocal.ThreadLocalMap,这是一个特殊的Map,key为ThreadLocal实例,值为ThreadLocal变量的实际值。ThreadLoca相当于一个转接口,连接Thread和ThreadLocal。

代码(4)可以看到如果当前线程的threadLocals变量为null,会调用ThreadLocal的setInitialValue方法初始化当前线程的threadLocals实例。

private T setInitialValue(Thread t) {
  T value = initialValue();
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    map.set(this, value);
  } else {
    createMap(t, value);
  }
  if (this instanceof TerminatingThreadLocal<?> ttl) {
    TerminatingThreadLocal.register(ttl);
  }
  if (TRACE_VTHREAD_LOCALS) {
    dumpStackIfVirtualThread();
  }
  return value;
}

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

setInitialValue方法会创建参数传递线程的threadLocals值,并且设置一个初始化值。从代码(1)可以看到threadLocals的key为ThreadLocal实例。

下面再看看ThreadLocal的set方法:

public void set(T value) {
  set(Thread.currentThread(), value);   // (1)
  if (TRACE_VTHREAD_LOCALS) {
    dumpStackIfVirtualThread();
  }
}

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

从代码(1)可以看到,调用ThreadLocald的set方法会向当前线程的threadLocals变量里设置传递的值value,key为ThreadLocal实例的引用,和get方法一样,如果当前线程的threadLocals变量为null,则会创建一个ThreadLocalMap变量并把value设置为初始值。

总结:在每个线程内部都有一个threadLocals变量,该变量类型为ThreadLocal.ThreadLocalMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中。

如果线程不销毁,那么对应的本地变量就会一直存在,所以可能存在内存溢出,因此使用完毕之后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals变量里的值。

注意:ThreadLocal不具备继承性,也就是说子线程并不能访问父线程的ThreadLocal变量。

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

相关文章

  • Java连接Redis报错:NoSuchElementException: Unable to validate object的解决办法

    Java连接Redis报错:NoSuchElementException: Unable to&nb

    这篇文章主要介绍了Java连接Redis报错:NoSuchElementException: Unable to validate object的解决办法,文中通过图文讲解的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2024-12-12
  • 详解Spring如何避免被JVM 垃圾回收

    详解Spring如何避免被JVM 垃圾回收

    如果Spring 被回收掉,Spring管理的bean全部会被回收,那我们的Java应用不就被一锅端了吗?所以本文小编将和大家一起聊聊Spring如何避免被JVM垃圾回收,需要的朋友可以参考下
    2023-11-11
  • Java通过动态规划设计股票买卖最佳时机

    Java通过动态规划设计股票买卖最佳时机

    动态规划可谓是大名鼎鼎,笔试面试中的高频考点,也是重点难点,动态规划类型题目灵活多变,难度系数也相对较高,往往我们做不好动态规划的题目就会与心仪的offer失之交臂,本篇文章我们就一起来研究一下动态规划设计股票买卖最佳时机
    2022-10-10
  • Java SWT中常见弹出框实例总结

    Java SWT中常见弹出框实例总结

    刚开始写Java工具的小伙伴可能不知道怎么写消息对话框,在这里总结一些常用的几种消息弹出框,下面这篇文章主要给大家介绍了关于Java SWT中常见弹出框的相关资料,需要的朋友可以参考下
    2023-01-01
  • 浅谈Java到底是值传递还是引用传递呢

    浅谈Java到底是值传递还是引用传递呢

    今天带大家学习Java的相关知识,文章围绕着Java到底是值传递还是引用传递展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Java中的StopWatch计时利器使用指南

    Java中的StopWatch计时利器使用指南

    StopWatch通常用于测量一段代码执行所花费的时间,它能够精确地记录开始时间、结束时间,并计算出这中间的时间差,下面给大家介绍Java中的StopWatch计时利器的深度解析与使用指南,感兴趣的朋友一起看看吧
    2025-05-05
  • Java中实现Comparator接口和用法实例(简明易懂)

    Java中实现Comparator接口和用法实例(简明易懂)

    这篇文章主要介绍了Java中实现Comparator接口和用法实例(简明易懂),本文给出实现Comparator接口的实例和使用这个接口的代码实例,需要的朋友可以参考下
    2015-05-05
  • JAVA“无法验证证书。将不执行该应用程序。”提示解决办法

    JAVA“无法验证证书。将不执行该应用程序。”提示解决办法

    这篇文章主要给大家介绍了关于JAVA“无法验证证书,将不执行该应用程序”提示的解决办法,要解决Java无法验证证书的问题,可以尝试下本文的方法,需要的朋友可以参考下
    2024-03-03
  • Spring MVC中使用Google kaptcha验证码的方法详解

    Spring MVC中使用Google kaptcha验证码的方法详解

    kaptcha 是一个非常实用的验证码生成工具。有了它,你可以生成各种样式的验证码,因为它是可配置的,下面这篇文章主要给大家介绍了关于Spring MVC中使用Google kaptcha验证码的方法,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-10-10
  • 解决IDEA 启动Tomcat控制台乱码问题

    解决IDEA 启动Tomcat控制台乱码问题

    今天在Idea中用Tomcat跑一个Web项目,启动后,Tomcat日志在控制台打印出来都是乱码,初次遇到这个问题真的很棘手,今天小编带领大家一起看看解决方法
    2018-07-07

最新评论