首页 > 编程语言 >JAVA线程基础——ThreadLocal的使用和原理

JAVA线程基础——ThreadLocal的使用和原理

时间:2024-09-12 12:22:29浏览次数:10  
标签:set JAVA 变量 Thread ThreadLocal 线程 threadLocals

一、ThreadLocal

        多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,如图 1-3所示。
        同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreadLocal 就可以做这件事情,虽然ThreadLocal 并不是为了解决这个问题而出现的。

                       


        ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个 ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存,如图 1-4所示。

Threadlocal 使用示例

public class ThreadLocalExample {

    // 创建一个ThreadLocal变量来保存每个线程的计数器
    private static final ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
        // 创建并启动两个线程
        Thread thread1 = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    // 获取当前线程的计数器,并增加
                    int counter = threadLocalCounter.get();
                    counter++;
                    threadLocalCounter.set(counter);
                    System.out.println("Thread 1 counter: " + threadLocalCounter.get());
                }
            } finally {
                // 清理ThreadLocal变量
                threadLocalCounter.remove();
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    // 获取当前线程的计数器,并增加
                    int counter = threadLocalCounter.get();
                    counter++;
                    threadLocalCounter.set(counter);
                    System.out.println("Thread 2 counter: " + threadLocalCounter.get());
                }
            } finally {
                // 清理ThreadLocal变量
                threadLocalCounter.remove();
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

        在这个示例中,我们创建了两个Thread对象,并为它们分别分配了不同的任务。每个任务都在一个try-finally块中执行,以确保无论任务是否成功完成,ThreadLocal变量最终都会被清理。

Threadlocal 的实现原理

首先看 ThreadLocal 关类 类图结构,如图1-5所示

        由该图可知,Thread 类中有一个threadLocals 和一个 inheritableThreadLocals,它们都是 ThreadLocalMap 类型的变量,而ThreadLocalMap是一个定制化的Hashmap。在默认情况下,每个线程中的这两个变量都为 nul,只有当前线程第一次调用 ThreadLocal 的 set 或者 get方法时才会创建它们。

        其实每个线程的本地变量不是存放在ThreadLocal 实例里面,而是存放在调用线程的threadLocals 变量里面。也就是说,ThreadLocal 类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadocals里面并存放起来,当调用线程调用它的get方法时,再从当前线程的 threadLocals 变量里面将其拿出来使用。如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals 变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。

        另外,Thread 里面的threadLocals为何被设计为map 结构?很明显是因为每个线程可以关联多个 ThreadLocal 变量

下面简单分析 ThreadLocal set  get  remove 方法的实现逻辑

       1.  void set(T value)
  public void set(T value) {
        //(1)获取当前线程
        Thread t = Thread.currentThread();
        //(2)将当前线程作为key,去查找对应的线程变量,找到则设置
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //(3)第一次调用就创建当前线程对应的HashMap 
            createMap(t, value);
    }

        代码 (1)先获取调用线程,然后使用当前 线程作为 参数 调用 getMap(t )方法 getMap(Thread t)的代码如下

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

        可以看到, getMap(t)的作用是获取线程 自己的变 threadLocals, threadlocal 变量被绑定到了线程的成员变量上。

        如果 getMap(t)的返回值不为空,则把 value 值设置到 threadLocals 中,也就是把当前值放入当前线程的内存变量 threadLocals中, threadLocals 是一个 HashMap 结构,其中key就是当前 ThreadLocal 实例对象引用, value 是通过set方法传递的值。

        如果 getMap(t)返回空值则说明是第一次调 set 方法,这时创建当前线程的 threadLocals 量。下面来看 createMap(t, value)做什么。

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

        它创建当前线程的 threadLocals 变量。

2. T get()
public T get() {
        //(4)获取当前线程
        Thread t = Thread.currentThread();
        //(5)获取当前threadlocals变量
        ThreadLocalMap map = getMap(t);
        //(6)如果threadlocals不为null,则返回对应本地变量的值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //(7)threadlocals为空则初始化当前线程的threadLocals成员变量
        return setInitialValue();
    }

代码( 4)首先获取当前线程实例,如果当线程的 threadLocals 变量不为 null ,则直接返回当前线程绑定的本地变量,否则执行代码(7)进行初始化。 setInitialValue()的代码如下。

 private T setInitialValue() {
        //(8)初始化为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //(9)如果当前线程的threadLocals变量不为空
        if (map != null)
            map.set(this, value);
        else
            //(10)如果当前线程的threadLocals变量为空
            createMap(t, value);
        return value;
    }
protected T initialValue() {
        return null;
    }

        如果当前线程的 threadLocals变量不为空,则设置当前线程的本地变量值为null,否则调用 createMap 方法建当前线程的 createMap变量。

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

        如以上代码所示,如果当前线程的threadLocals 变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量。

总结

        如图1-6所示,在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this引用,value 则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用 ThreadLocal的remove 方法删除对应线程的threadLocals中的本地变量。

ThreadLoal 不支持继承性

        首先看一个例子

public class TestThreadLocal {
    //(1)创建线程变量
    public static ThreadLocal<String> threadlocal = new ThreadLocal<String>();
    public static void main(String[] args) {
        threadlocal.set("hello world");
        //(3)启动子线程
        Thread thread = new Thread(new Runnable() {
            public void run() {
                //(4)子线程输出线程变量的值
                System.out.println("thread:" + threadlocal.get());
            }
        });
        thread.start();
        //(5)主线程输出线程变量的值
        System.out.println("main:" + threadlocal.get());
    }
}

        也就是说,同一个 ThreadLocal 变量在父线程中被设置值后,在子线程中是获取不到的。根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程为thread 线程,而这里调用set方法设置线程变量的是 main 线程,两者是不同的线程,自然子线程访问时返回nul。那么有没有办法让子线程能访问到父线程中的值?答案是有的,请看下一篇文章。

标签:set,JAVA,变量,Thread,ThreadLocal,线程,threadLocals
From: https://blog.csdn.net/weixin_45826852/article/details/142094082

相关文章

  • Java学习路线:详细指引
    Java学习路线可以分为几个阶段,每个阶段都有其重点和推荐学习的内容。下面我将按照初学者、进阶和高级三个阶段来举例说明:初学者阶段目标:熟悉Java基础语法理解面向对象编程掌握基本数据类型和数据结构学会使用IDE(如IntelliJIDEA或Eclipse)学习内容:Java基础语法:包括变量、......
  • JAVA基础:抽象类,接口,instanceof,类关系,克隆
    1JDK中的包JDK=JRE+开发工具集(javac.exe)JRE=JVM+java类库JVM=java虚拟机jdk中自带了许多的包(类),常用的有java.lang该包中的类,不需要引用,可以直接使用。例如:Object,System,Stringjava.utiljava.sqljava.netjava.iojava.text2抽象方法......
  • 【开源免费】基于SpringBoot+Vue.JS在线视频教育平台(JAVA毕业设计)
    本文项目编号T027,文末自助获取源码\color{red}{T027,文末自助获取源码}......
  • 【开源免费】基于SpringBoot+Vue.JS校园管理系统(JAVA毕业设计)
    本文项目编号T026,文末自助获取源码\color{red}{T026,文末自助获取源码}......
  • Java中的Switch语句:从基本类型到String和枚举的进化
    Java中的Switch语句:从基本类型到String和枚举的进化在Java编程中,switch语句是一个强大且常用的控制结构,用于根据变量的值执行不同的代码块。从最初的只支持基本数据类型,到后来的支持String和枚举类型,switch语句的功能不断扩展,使其更加灵活和强大。今天,我们就来深入探讨Java......
  • Java中的继承和重写
    ###Java中的继承关系继承是面向对象编程(OOP)中的一个核心概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以重用父类的代码,并且可以在不修改父类的情况下扩展或修改其行为。在Java中,继承关系通过`extends`关键字来实现。####1.继承的基本概念在Java中,......
  • 使用java程序对字符串进行加密
    程序功能程序的功能是对用户输入的字符串,使用常见的三种加密算法(MD5、SHA-1和SHA-256)进行加密,并输出每种算法加密后的结果。主要步骤包括:用户通过控制台输入一个字符串。程序使用MessageDigest类,对输入的字符串分别进行MD5、SHA-1和SHA-256算法的加密处理。每......
  • 28 Java中的循环结构
    Java中的循环结构在Java编程中,循环结构是实现重复执行代码块的关键工具。通过循环结构,程序员可以高效地处理大量数据、执行重复任务,从而简化代码并提高程序的效率。本文将深入探讨Java中的循环结构,帮助你全面理解其工作原理及实际应用。1.前置知识在深入探讨循环结构之......
  • Java反射语法
    1.反射1.1反射的概述【理解】反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。1.2获取Class类对象的三种方式【应用】三种......
  • Java技术深度探索:高并发场景下的线程安全与性能优化
    Java技术深度探索:高并发场景下的线程安全与性能优化在当今的软件开发领域,随着互联网应用的日益复杂和用户量的激增,高并发成为了一个不可忽视的技术挑战。Java,作为一门广泛应用于企业级开发的编程语言,其内置的并发支持机制如线程(Thread)、锁(Lock)、并发集合(ConcurrentCollect......