• Java进阶篇--并发容器之ThreadLocal


    目录

    ThreadLocal的简介

    ThreadLocal的实现原理

    ThreadLocalMap详解

    Entry的数据结构

    set()方法

    getEntry()方法

    remove()方法

    ThreadLocal的应用场景 


    ThreadLocal的简介

    ThreadLocal可以被理解为线程的本地变量。它提供了一种将变量与线程关联起来的机制,使得每个线程都可以拥有自己独立的变量副本,互不干扰。

    多线程编程中通常解决线程安全的问题,我们会利用synchronzed或者lock控制线程对临界区资源的同步顺序从而解决线程安全的问题,但是这种加锁的方式会让未获取到锁的线程进行阻塞等待,很显然这种方式的时间效率并不是很好。并且共享变量的访问往往是一个潜在的线程安全问题。通过使用ThreadLocal,我们可以为每个线程创建一个独立的变量副本,使得每个线程都可以独立地操作该变量,而不会对其他线程产生影响。这种方式实际上是通过空间换时间的方式,通过增加内存占用来避免线程间的竞争和同步机制。

    ThreadLocal类实际上是一个容器,内部维护了一个ThreadLocalMap,其中的键值对以线程作为键,变量副本作为值。每个线程访问ThreadLocal变量时,实际上是在操作自己的变量副本,线程之间互不干扰。

    需要注意的是,使用ThreadLocal时要注意及时清理不再使用的变量副本,以避免内存泄漏。通常在使用完ThreadLocal后,应该调用其remove方法移除当前线程的变量副本。

    总结起来,ThreadLocal提供了一种简便的方式,使得每个线程都可以拥有自己独立的变量副本,实现线程间的数据隔离,从而避免了一些多线程环境下的竞争和同步问题。这是一种以空间换时间的策略,通过增加内存占用来提高程序的并发性能和线程安全性。

    ThreadLocal的实现原理

    ThreadLocal的实现原理是通过每个线程维护一个ThreadLocalMap来实现。ThreadLocal是一个泛型类,通过调用set方法将值存放在当前线程的ThreadLocalMap中,以ThreadLocal实例为key,值为value进行存储。而get方法则是通过ThreadLocal实例作为key从ThreadLocalMap中获取对应的值。

    具体实现原理如下:

    1. ThreadLocal类中定义了一个静态内部类ThreadLocalMap,该类是ThreadLocal的一个成员变量,用于存储线程局部变量。

    2. 在调用ThreadLocal的set方法时,会首先获取当前线程的ThreadLocalMap对象。如果ThreadLocalMap对象存在,则以当前ThreadLocal实例为key,将value存储在ThreadLocalMap中。如果ThreadLocalMap对象不存在,则创建一个新的ThreadLocalMap对象,并将其与当前线程关联。

    3. 在调用ThreadLocal的get方法时,同样会先获取当前线程的ThreadLocalMap对象。然后通过ThreadLocal实例作为key,从ThreadLocalMap中取出对应的值。

    4. 在调用ThreadLocal的remove方法时,会先获取当前线程的ThreadLocalMap对象,然后从ThreadLocalMap中删除以当前ThreadLocal实例为key的键值对。

    总结一下,ThreadLocal的实现原理就是通过每个线程维护一个ThreadLocalMap,使用ThreadLocal实例作为key,将值存储在ThreadLocalMap中,实现了线程间的隔离和数据的独立访问。这样就保证了每个线程都可以独立地访问自己的ThreadLocal变量,而不会受到其他线程的干扰。

    1. public class main {
    2. // 创建一个ThreadLocal对象,用于存储线程局部变量
    3. private static ThreadLocal threadLocal = new ThreadLocal<>();
    4. public static void main(String[] args) {
    5. // 在主线程中设置ThreadLocal的值
    6. threadLocal.set("MainValue");
    7. // 创建两个子线程并启动
    8. Thread thread1 = new Thread(new MyRunnable("Thread1"));
    9. Thread thread2 = new Thread(new MyRunnable("Thread2"));
    10. thread1.start();
    11. thread2.start();
    12. try {
    13. // 等待两个子线程执行完毕
    14. thread1.join();
    15. thread2.join();
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. // 获取主线程中的ThreadLocal的值并打印
    20. System.out.println("主线程中的ThreadLocal的值:" + threadLocal.get());
    21. // 调用remove方法清除主线程中的ThreadLocal的值
    22. threadLocal.remove();
    23. // 再次获取主线程中的ThreadLocal的值并打印
    24. System.out.println("主线程中的ThreadLocal的值:" + threadLocal.get());
    25. }
    26. // 自定义Runnable类,用于在每个线程中访问ThreadLocal变量
    27. private static class MyRunnable implements Runnable {
    28. private String name;
    29. public MyRunnable(String name) {
    30. this.name = name;
    31. }
    32. @Override
    33. public void run() {
    34. // 在当前线程中设置ThreadLocal的值
    35. threadLocal.set(name + "Value");
    36. // 在当前线程中获取ThreadLocal的值并打印
    37. System.out.println(name + "线程中的ThreadLocal的值:" + threadLocal.get());
    38. // 调用remove方法清除当前线程中的ThreadLocal的值
    39. threadLocal.remove();
    40. // 再次获取当前线程中的ThreadLocal的值并打印
    41. System.out.println(name + "线程中的ThreadLocal的值:" + threadLocal.get());
    42. }
    43. }
    44. }

    代码分析:

    1. 在ThreadLocalExample类中,创建一个ThreadLocal对象 threadLocal,用于存储字符串类型的线程局部变量。
    2. 在main方法中,首先在主线程中使用threadLocal.set("MainValue")设置threadLocal的值为"MainValue"。
    3. 创建两个子线程 thread1 和 thread2,并分别传入不同的名称参数。
    4. 在自定义的MyRunnable类中,构造方法接收一个名称参数,并将其保存在私有字段中。
    5. 在run方法中,首先在当前线程中使用threadLocal.set(name + "Value")设置threadLocal的值为当前线程名和"Value"拼接而成的字符串。
    6. 然后在当前线程中使用threadLocal.get()获取threadLocal的值,并将其打印出来。
    7. 在当前线程中调用threadLocal.remove()方法,清除当前线程中的threadLocal的值。
    8. 再次在当前线程中使用threadLocal.get()获取threadLocal的值,并将其打印出来。
    9. 在main方法中,启动两个子线程并等待它们执行完毕。
    10. 获取主线程中的threadLocal的值,并打印出来。
    11. 调用threadLocal.remove()方法,清除主线程中的threadLocal的值。
    12. 再次获取主线程中的threadLocal的值,并打印出来。

    注意:使用了多线程来访问和设置ThreadLocal变量。由于多线程的执行顺序和调度是不确定的,因此每次运行的结果可能会不同。

    因为在多线程环境下,每个线程都有自己的副本,称为线程局部变量。而ThreadLocal对象就是用来存储线程局部变量的。在代码中,主线程设置了threadLocal的值为"MainValue",而两个子线程分别设置了threadLocal的值为"Thread1Value"和"Thread2Value"。

    由于线程调度的不确定性,不同的线程可能会以不同的顺序运行,这导致了每次运行的结果可能会不同。例如,在某一次运行中,主线程先执行,然后是Thread1线程,最后是Thread2线程;而在另一次运行中,可能是Thread2线程先执行,然后是Thread1线程,最后是主线程。

    因此,每次运行的输出结果取决于各个线程的执行顺序和调度情况,所以会出现不一样的结果。

    ThreadLocalMap详解

    ThreadLocalMap是ThreadLocal的核心实现类,用于存储线程局部变量。ThreadLocalMap是一个自定义的哈希表,它使用了开放地址法来解决哈希冲突。每个Thread对象都有自己的ThreadLocalMap对象,用于存储线程局部变量。

    ThreadLocalMap中的每个元素都是一个Entry对象,Entry包含了线程局部变量的键值对,其中键是ThreadLocal对象,值是线程局部变量的值。Entry内部还包含了一些相关的操作方法,例如get()方法获取线程局部变量的值,set()方法设置线程局部变量的值等。下面分别介绍Entry的数据结构、set()方法、getEntry()方法和remove()方法。

    Entry的数据结构

    1. static class Entry extends WeakReference> {
    2. /** 与此 ThreadLocal 关联的值。*/
    3. Object value;
    4. Entry(ThreadLocal k, Object v) {
    5. super(k);
    6. value = v;
    7. }
    8. }

    可以看到,Entry类继承了WeakReference>,通过继承WeakReference来实现对ThreadLocal对象的弱引用,以便在不需要时可以被垃圾回收。value字段存储线程局部变量的值。 

    set()方法

    1. public void set(ThreadLocal key, Object value) {
    2. // 获取当前线程的ThreadLocalMap对象
    3. ThreadLocalMap map = getMap(Thread.currentThread());
    4. if (map != null)
    5. // 如果ThreadLocalMap不为null,则将key和value放入map中
    6. map.set(key, value);
    7. else
    8. // 如果ThreadLocalMap为null,则创建一个新的ThreadLocalMap对象,并将key和value放入其中
    9. createMap(key, value);
    10. }

    set()方法用于设置线程局部变量的值,首先通过getMap(Thread.currentThread())方法获取当前线程的ThreadLocalMap对象,如果该对象不为null,则将键值对放入ThreadLocalMap中;否则,调用createMap(key, value)方法创建一个新的ThreadLocalMap对象,并将键值对放入其中。 

    getEntry()方法

    1. private Entry getEntry(ThreadLocal key) {
    2. int i = key.threadLocalHashCode & (table.length - 1);
    3. Entry e = table[i];
    4. if (e != null && e.get() == key) return e;
    5. else return getEntryAfterMiss(key, i, e);
    6. }
    7. private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    8. Entry[] tab = table;
    9. int len = tab.length;
    10. while (e != null) {
    11. ThreadLocal k = e.get();
    12. if (k == key)
    13. return e;
    14. if (k == null)
    15. expungeStaleEntry(i);
    16. else
    17. i = nextIndex(i, len);
    18. e = tab[i];
    19. }
    20. return null;
    21. }

    getEntry()方法用于获取与指定ThreadLocal对象关联的Entry对象。首先通过key.threadLocalHashCode & (table.length - 1)计算哈希值,然后在table数组中查找对应位置上的Entry对象。如果找到的Entry对象不为null且其引用的ThreadLocal对象与指定ThreadLocal对象相同,则返回该Entry对象;否则,调用getEntryAfterMiss(key, i, e)方法在table数组中的其他位置继续查找。

    如果在查找的过程中发现Entry对象的引用为null,则需要调用expungeStaleEntry(i)方法将table数组中对应位置的Entry对象置为null。如果最终仍然没有找到与指定ThreadLocal对象关联的Entry对象,则返回null。

    remove()方法

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

    remove()方法用于清除当前线程中指定ThreadLocal对象的值。首先通过getMap(Thread.currentThread())方法获取当前线程的ThreadLocalMap对象,如果该对象不为null,则调用m.remove(this)方法将指定ThreadLocal对象的值清除掉;否则,不做任何操作。

    ThreadLocal的应用场景 

    ThreadLocal的主要作用是提供线程局部变量,即每个线程都拥有自己的变量副本,互相之间不会相互干扰。 ThreadLocal 的使用场景如下:

    • 并发控制:在多线程环境下,可以使用ThreadLocal来存储每个线程独有的变量,避免线程间数据的共享和竞争条件。
    • 上下文信息传递:在跨多个方法或模块的代码中,可以使用ThreadLocal传递上下文信息,而不必显式地传递参数。这在某些框架和工具,如Web应用程序中的请求上下文或用户信息等方面特别有用。
    • 数据库连接管理:在多线程环境下,使用ThreadLocal可以为每个线程维护一个独立的数据库连接,确保线程安全和高效的数据库操作。
    • 事务管理:在基于线程的事务管理中,可以使用ThreadLocal存储事务上下文,使得不同的线程能够独立地进行事务处理,而不会冲突。
    • 线程安全的日期格式化:SimpleDateFormat是非线程安全的,但可以通过ThreadLocal在每个线程中创建一个独立的SimpleDateFormat实例,以便进行日期的格式化和解析。
    • 缓存管理:可以使用ThreadLocal来缓存一些需要在同一线程中共享的数据,避免频繁的计算或查询操作。

    需要注意的是,在使用ThreadLocal时,要避免内存泄漏问题。因为ThreadLocal弱引用的特性,如果没有及时清理线程的引用,可能会导致对象无法回收,造成内存泄漏。因此,在不再使用ThreadLocal时,要调用remove()方法主动清除数据,或者使用ThreadLocal的initialValue()方法设置初始值,以确保资源能够正确释放。

  • 相关阅读:
    NSSCTF第12页(3)
    MySQL 相关英文单词
    精品基于Javaweb的酒店民宿管理推荐平台SSM
    谷歌云计算技术基础架构,谷歌卷积神经网络
    Threejs_04 gui调试开发
    大模型培训 AUTOWEBGLM:自动网页导航智能体
    如何把路由器设备的LAN口地址为三大私网地址
    【基于simulink的二阶电路仿真】
    解决Mac下pycharm不提示element.click()方法
    会议OA项目之我的审批
  • 原文地址:https://blog.csdn.net/m0_74293254/article/details/133959526