• ThreadLocal


    目录

    场景:

     实现:

    那么ThreadLocalMap又是啥呢?

    ThreadLocalMap的底层结构

    那么ThreadLocalMap为什么要用数组呢?

    那么set对象存放在哪里?

    如何共享ThreadLocal数据,让多个线程访问

    内存泄露问题

    ThreadLocal练习


    前置:

     ThreadLocal——线程本地存储。也就是线程本地变量的管理者,一般用于实现线程隔离(数据隔离),在线程中使用它存储数据,仅在当前线程访问使用

    其实,它仅仅是一个管理者,真正存储数据的不是它,而是一个叫做ThreadLocalMap的对象,每个线程都有一个自己的TreadLocalMap对象(在Thread中对象名为:threadLocals或者inheritableThreadLocals)。

    场景:

    比如JDBC,我们每个线程进行数据库操作,连接都有自己独有的,而不会出现a连接到b上面

     实现:

    这里我们会体现他是怎么做到线程隔离的

    1. ThreadLocal<String> localName = new ThreadLocal();
    2. localName.set("张三");
    3. String name = localName.get();
    4. localName.remove();

    set源代码

    会发现,设置值->会先获取当前线程,根据当前线程得到它的ThreadLocalMap,然后通过ThreadLocalMap赋值

    那么ThreadLocalMap又是啥呢?

    从源码可知,ThreadLocalMap其实就是Thread中的一个叫threadLocals的变量获取的

    所以说,数据的逻辑是在Thread当前线程中,而不是在ThreadLocal,它相当于只是一个管理类

    1. public void set(T value) {
    2. Thread t = Thread.currentThread();// 获取当前线程
    3. ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
    4. if (map != null) // 校验对象是否为空
    5. map.set(this, value); // 不为空set
    6. else
    7. createMap(t, value); // 为空创建一个map对象
    8. }
    1. ThreadLocalMap getMap(Thread t) {
    2. return t.threadLocals;
    3. }

    可以发现每个Thread都维护自己的ThreadLocals变量,当每个线程创建ThreadLocal时候,实际上数据是存在ThreadLocals里面的,从而实现隔离,所以说数据隔离的逻辑在Thread中;

    1. public class Thread implements Runnable {
    2. ……
    3. /* ThreadLocal values pertaining to this thread. This map is maintained
    4. * by the ThreadLocal class. */
    5. ThreadLocal.ThreadLocalMap threadLocals = null;
    6. /*
    7. * InheritableThreadLocal values pertaining to this thread. This map is
    8. * maintained by the InheritableThreadLocal class.
    9. */
    10. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    11. ……

    ThreadLocalMap的底层结构

    类似于HashMap,其实不然,源码可知是一个Entry->就是一个键值对对象,继承WeakReference弱引用,key是ThreadLocal的引用,但是存储数据的逻辑是给到Thread本身,就像在公司做事一样,我现在在这个公司做事,我是Thread,事情是value,在的地方是ThreadLocal

    (22条消息) Java中Map集合中的Entry对象_宇智波爱编程的博客-CSDN博客_entry

    1. static class ThreadLocalMap {
    2. static class Entry extends WeakReference<ThreadLocal<?>> {
    3. /** The value associated with this ThreadLocal. */
    4. Object value;
    5. Entry(ThreadLocal<?> k, Object v) {
    6. super(k);
    7. value = v;
    8. }
    9. }
    10. ……
    11. }

    其实可以发现,本身ThreadLocalMap维护的是一个哈希表一样的结构,本质也就是数组

    那么ThreadLocalMap为什么要用数组呢?

    之前我们说过,ThreadLocal就像是维护了一个地方,存储值的逻辑还是在Thread本身,将值存在ThreadLocalMap中,而ThreadLocalMap维护的是一个哈希表,每个Entry都是一个键值对,键就是在哪做事(ThreadLocal),对就是值,所以说我们Thread是可以做很多事的,做的地方也可以不一样(可以创建多个ThreadLocal对象),只是说逻辑都在当前Thread本身上,所以肯定用数组来存;

    场景:开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里;

    1. static class ThreadLocalMap {
    2. static class Entry extends WeakReference<ThreadLocal<?>> {
    3. /** The value associated with this ThreadLocal. */
    4. Object value;
    5. Entry(ThreadLocal<?> k, Object v) {
    6. super(k);
    7. value = v;
    8. }
    9. }
    10. private static final int INITIAL_CAPACITY = 16;
    11. private Entry[] table;

    那么set对象存放在哪里?

    (22条消息) JVM-01(阶段性学习)_Fairy要carry的博客-CSDN博客

    因为是线程,众所周知,每个线程都会有自己的栈内存,,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

    注:当然我们new 的ThreadLocal实例虽然被Thread给引用,进行了一个持有的操作,但是new嘛,所以也是位于堆上的;

    如何共享ThreadLocal数据,让多个线程访问

    使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。

    1. private void test() {
    2. final ThreadLocal threadLocal = new InheritableThreadLocal();
    3. threadLocal.set("帅得一匹");
    4. Thread t = new Thread() {
    5. @Override
    6. public void run() {
    7. super.run();
    8. Log.i( "张三帅么 =" + threadLocal.get());
    9. }
    10. };
    11. t.start();
    12. }

    内存泄露问题

    我们首先回顾一下ThreadLocalMap,里面的Entry继承了WeakReference弱引用

    说明ThreadLocal在保存数据时会吧自己当做key存在ThreadLocalMap中,也就是说ThreadLocal会被ThreadLocalMap持有引用(也就是Thread当前线程),正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了

     (22条消息) 垃圾回收流程-简单叙述_Fairy要carry的博客-CSDN博客_垃圾回收过程

    导致问题:

    ThreadLocal因为在Thread本身逻辑里面是没有被强引用的,所以发生GC时候会被回收,但是value会暴露出来

    这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露;

    如何解决:

    最后remove即可,就可以把值给清空了

    1. ThreadLocal<String> localName = new ThreadLocal();
    2. try {
    3. localName.set("张三");
    4. ……
    5. } finally {
    6. localName.remove();
    7. }

    remove逻辑:把所有对应的值都置空

    1. private void remove(ThreadLocal<?> key) {
    2. Entry[] tab = table;
    3. int len = tab.length;
    4. int i = key.threadLocalHashCode & (len-1);
    5. for (Entry e = tab[i];
    6. e != null;
    7. e = tab[i = nextIndex(i, len)]) {
    8. if (e.get() == key) {
    9. e.clear();
    10. expungeStaleEntry(i);
    11. return;
    12. }
    13. }
    14. }

    那么问题来了,为什么要把ThreadLocalMap的key设计成弱引用?

    key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。

    ThreadLocal练习

    1. //主线程中
    2. ThreadLocal<String> k1 = new ThreadLocal<>();
    3. ThreadLocal<Integer> k2 = new ThreadLocal<>();
    4. ThreadLocal<Object> k3 = new ThreadLocal<>();
    5. k1.set("main thread");
    6. k2.set(1000);
    7. k3.set("hallo~");
    8. Thread thread1 = new Thread(new Runnable() {
    9. @Override
    10. public void run() {
    11. System.out.println("thread1:"+k1.get());
    12. System.out.println("thread1:"+k2.get());
    13. System.out.println("thread1:"+k3.get());
    14. k1.set("123");
    15. k2.set(1);
    16. k3.set("sss");
    17. System.out.println("thread1:"+k1.get());
    18. System.out.println("thread1:"+k2.get());
    19. System.out.println("thread1:"+k3.get());
    20. System.out.println();
    21. }
    22. });
    23. Thread thread2 = new Thread(new Runnable() {
    24. @Override
    25. public void run() {
    26. try {
    27. Thread.sleep(1000);
    28. } catch (InterruptedException e) {
    29. e.printStackTrace();
    30. }
    31. System.out.println("thread2:"+k1.get());
    32. System.out.println("thread2:"+k2.get());
    33. System.out.println("thread2:"+k3.get());
    34. k1.set("abc");
    35. k2.set(2);
    36. k3.set(k3);
    37. System.out.println("thread2:"+k1.get());
    38. System.out.println("thread2:"+k2.get());
    39. System.out.println("thread2:"+k3.get());
    40. System.out.println();
    41. }
    42. });
    43. Thread thread3 = new Thread(new Runnable() {
    44. @Override
    45. public void run() {
    46. try {
    47. Thread.sleep(2000);
    48. } catch (InterruptedException e) {
    49. e.printStackTrace();
    50. }
    51. System.out.println("thread3:"+k1.get());
    52. System.out.println("thread3:"+k2.get());
    53. System.out.println("thread3:"+k3.get());
    54. k1.set("haha");
    55. k2.set(10);
    56. k3.set(new Object());
    57. System.out.println("thread3:"+k1.get());
    58. System.out.println("thread3:"+k2.get());
    59. System.out.println("thread3:"+k3.get());
    60. System.out.println();
    61. }
    62. });
    63. thread1.start();
    64. thread2.start();
    65. thread3.start();
    66. try {
    67. Thread.sleep(3000);
    68. } catch (InterruptedException e) {
    69. e.printStackTrace();
    70. }
    71. System.out.println("main:"+k1.get());
    72. System.out.println("main:"+k2.get());
    73. System.out.println("main:"+k3.get());

  • 相关阅读:
    Windows端ZLMediaKit编译与webrtc推拉流测试
    11链表-迭代与递归
    CobalStrike(CS)上线隐藏IP和流量
    ECCV 2022 | OA-MIL:目标感知多实例学习方法
    【Rust 入门学习】1.2 使用 Cargo 构建 Rust 项目
    [前端框架]-VUE(上篇)
    知识干货:基础存储服务新手体验营
    Feign(黑马程序员)
    mysql使用--分组查询
    指针拔尖1——(看完包会,不会来打我)
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/125569798