【Java】线程数据共享和安全 -ThreadLocal

简介: 今天我要为大家推荐一个Java中非常实用且神奇的工具——ThreadLocal。它可以让我们在多线程环境下,轻松地实现线程私有的数据存储。它可以帮助我们在多线程环境下轻松解决变量共享和线程安全的问题。

目录


🎉 Java的有利武器:ThreadLocal


🚀 第一章 - 什么是ThreadLocal?


📝第二章 - ThreadLocal原理


 📌源码分析


📜 第三章 - 如何使用ThreadLocal


🌟 第四章 - ThreadLocal的应用场景


🔮总结


🎉 Java的有利武器:ThreadLocal 🎉

📣今天我要为大家推荐一个Java中非常实用且神奇的工具——ThreadLocal。它可以让我们在多线程环境下,轻松地实现线程私有的数据存储。它可以帮助我们在多线程环境下轻松解决变量共享和线程安全的问题。🔍


🚀 第一章 - 什么是ThreadLocal?

1. ThreadLocal 的作用,可以实现在同一个线程数据共享 , 从而解决多线程数据安全问题 .

2. ThreadLocal 可以给当前线程关联一个数据 ( 普通变量、对象、数组 )set 方法 [ 源码 !]

3. ThreadLocal 可以像 Map 一样存取数据, key 为当前线程 , get 方法

4. 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数 据,就需要使用多个 ThreadLocal 对象实例

5. 每个 ThreadLocal 对象实例定义的时候,一般为 static 类型

6. ThreadLocal 中保存数据,在线程销毁后,会自动释放

📷 图一:ThreadLocal示意图



📝第二章 - ThreadLocal原理

⚠️一个ThreadLocal可以同时给多个线程分别关联各自的数据。每个线程在访问ThreadLocal时,都会获取到自己独立的数据副本,互不干扰。这就是ThreadLocal的独立性和隔离性所表现出来的特点。


📷 图二:ThreadLocal同时给多个线程分别关联各自的数据示意图



⚠️ThreadLocal是用于实现线程局部变量的机制,它为每个线程提供了一个独立的副本,保证了线程的隔离性。因此,一个ThreadLocal变量最多只能关联一个线程的数据。


📷 图三:ThreadLocal一个线程一个数据示意图



 📌源码分析

实际上,ThreadLocal本身并没有存储任何对象,所有的东西都存储在Thread对象本身里!



首先,在Thread对象(注意,不是ThreadLocal!)里有个成员变量:


ThreadLocal.ThreadLocalMap threadLocals = null;


这个成员变量的类型是ThreadLocal.ThreadLocalMap,这是ThreadLocal的一个内部类,可以理解为一个简单的HashMap(关于这个内部类的实现本篇就不详细展开了,有兴趣的同学可以看源码),就是保存键值对的一个容器。


其实,所有set到ThreadLocal上的对象,实际都保存在当前Thread中的threadLocals成员变量里。


为了说的清楚,下面我用伪代码的形式,写一下ThreadLocal的Set()方法的核心逻辑:

public void set(T value) {
        Thread t = Thread.currentThread(); //先获得当前线程
        if (t.threadLocals != null){ //判断当前线程上的threadLocals成员是否为空
            t.threadLocals.set(this, value); //如果threadLocals不等于空,则set value,ThreadLocal对象作为key值。
        }else{
            t.threadLocals = new ThreadLocalMap(); //如果threadLocals为空,则创建之然后再set value。
            t.threadLocals.set(this, value); 
        }
    }


通过以上代码大家应该都能看清楚,真正的value值是保存在Thread对象上的,同时,key值是ThreadLocal对象。


再看看get方法,依然是只有核心逻辑的伪代码:

public T get() {
        Thread t = Thread.currentThread(); //先获得当前线程
        if (t.threadLocals != null) { //判断当前线程上的threadLocals成员是否为空
            return (T)t.threadLocals.get(this); //不为空,则以ThreadLocal对象作为key值得到value,然后返回。
        }
        return setInitialValue(); //返回初始值
    }

理清了get和set方法,相信整个ThreadLocal的实现方式,大家应该都比较清楚了。



来自简书. 作者:我爱纽约先生的一段话(链接在文末)


作者之所以这样设计,我相信是经过了充分考虑的。我能想到的一点,就是考虑了对象的生命周期。


ThreadLocal要为每个线程维护value值,所以它的生命周期一般是长于线程对象生命周期的。一个长生命周期的对象维护一个短生命周期对象的引用,就有可能内存泄露,除非开发者要手动remove掉。


而把value存放在Thread上,一切就可以完美的解决。因为这个value对象是专门为Thread对象而存在的,所以value对象和Thread对象的生命周期应该完全一致。把value对象存放在Thread对象里,则不会有内存泄露的风险。虽然,Thread会维护一个ThreadLocal的引用(实际上是个弱引用),但是如前所说,一个短生命周期的对象维护长生命周期对象的引用,一般没什么问题。



📜 第三章 - 如何使用ThreadLocal

使用ThreadLocal非常简单。我们只需创建一个ThreadLocal对象,并重写initialValue()方法,定义每个线程初始的变量值。然后,通过调用get()方法可以获取当前线程的副本,而set()方法用于设置线程局部变量的值。

public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            // 设置每个线程的初始值为0
            return 0;
        }
    };
    public static void main(String[] args) {
        // 创建3个线程执行任务
        Thread t1 = new Thread(new MyRunnable());
        Thread t2 = new Thread(new MyRunnable());
        Thread t3 = new Thread(new MyRunnable());
        t1.start();
        t2.start();
        t3.start();
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            int value = threadLocal.get();
            System.out.println("线程:" + Thread.currentThread().getName() + ",初始值:" + value);
            // 修改初始值
            value += 5;
            threadLocal.set(value);
            // 再次获取修改后的值
            int updatedValue = threadLocal.get();
            System.out.println("线程:" + Thread.currentThread().getName() + ",修改后的值:" + updatedValue);
        }
    }
}


在上述代码中,我们创建了一个名为threadLocal的ThreadLocal对象,并重写了它的initialValue()方法,用于定义每个线程初始的变量值(这里设置为0)。


然后,我们创建了3个线程,并在每个线程的run()方法中操作ThreadLocal对象。首先,通过get()方法获取当前线程的副本,并输出初始值。然后,我们使用set()方法修改初始值,并再次使用get()方法获取修改后的值。


运行这段代码,你会看到每个线程都有自己独立的初始值和修改后的值。这表明每个线程通过ThreadLocal对象进行了数据隔离,互不干扰。


你可以根据自己的需求来定义ThreadLocal对象的值和操作逻辑。


🌟 第四章 - ThreadLocal的应用场景

ThreadLocal的应用非常广泛,下面为大家介绍几个特别实用的场景:


1️⃣ 数据库连接管理:在多线程环境下,使用ThreadLocal可以方便地管理数据库连接,每个线程都能获取到自己独立的连接副本,避免了线程安全和资源竞争问题。


2️⃣ Web应用用户信息存储:对于一个Web应用程序,可以使用ThreadLocal将当前用户的登录信息存储在线程局部变量中,这样在后续的请求处理过程中就无需传递这些信息了。


3️⃣ 日志跟踪:ThreadLocal还可以用于日志跟踪,每个线程都有自己的日志副本,方便调试和定位问题。


🔮总结

💡Thread里面存储着各自的ThreadLocalMap, 并且Thread的每一个ThreadLocal会根据Thread的生命周期进行销毁, 每个ThreadLocalMap都属于某一个Thread, 而ThreadLocal只是ThreadLocalMap里面的一个而已, ThreadLocal对象实例就是Map中的键, 根据它就可以得到value, 一个ThreadLocal只能为一个Thread服务一个数据, 但是同时可以为其他Thread服务.


💡为什么是一个ThreadLocal给Thread关联一个数据呢?


因为set()方法中, t.threadLocals.set(this, value); //如果threadLocals不等于空,则set value,ThreadLocal对象作为key值。


不管你怎么弄都是一个ThreadLocal对应着一个相应的Thread的


📷 图四:ThreadLocal原理图



🎉 精彩的ThreadLocal世界就在眼前!无论你是面对高并发的系统,还是只是想学习Java多线程编程,ThreadLocal都能帮助你轻松解决问题。希望这篇推荐能给你带来灵感与启发。如果你有其他关于ThreadLocal的经验和想法,也欢迎在评论区与我分享。下次再见!


相关文章
|
20天前
|
自然语言处理 Java 关系型数据库
Java|小数据量场景的模糊搜索体验优化
在小数据量场景下,如何优化模糊搜索体验?本文分享一个简单实用的方案,虽然有点“土”,但效果还不错。
28 0
|
7天前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
|
1月前
|
Java 中间件 调度
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
69 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
|
2月前
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
|
2月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
110 23
|
1月前
|
数据采集 存储 网络协议
Java HttpClient 多线程爬虫优化方案
Java HttpClient 多线程爬虫优化方案
|
2月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
182 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
3月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
233 60
【Java并发】【线程池】带你从0-1入门线程池
|
3月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
204 14
|
3月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
87 13