网上有很多关于 ThreadLocal 的分享,都挺不错的,这里简单记录一下我的理解。

前提

任何一个变量,如果它是存储在 堆内存\共享内存 中的数据,那么在多线程访问的情况下,必然出现一致性的问题。

为了解决这个问题,同时也是为了屏蔽底层硬件的差异性,JMM规范中描述了任何一个线程访问堆内存(共享内存)中的变量,为了满足变量在多线程之间的可见性。Java 提供了多种机制用于同步多线程之间的共享资源的交互。

  • Synchronization,依赖于Object的monitor对象,每一个object都有一个monitor对象,lock和unlock是自动执行的,在字节码语义中是enter monitor,和exit monitor。
  • valatile变量的读和写操作。
  • Java并发包提供的类。

堆内存/共享内存: 多个线程之间的共享内存。所有实例字段、静态字段以及数组元素都存储在堆内存中,而局部变量、方法参数以及异常参数存在在线程栈上,不会被内存模型所影响。

内存模型的问题无时无刻不在出现,其实最终还是共享资源的问题,不过这个共享资源是不可避免的,是演进过程之中的一个必然问题。

而内存模型归结下来又是2个处理方式,其一是读,其一是写。这又回到了读写互斥的问题上了。

ThreadLocal

上边简单的描述了变量如果存储在共享内存中必然面临的数据同步的问题,那么如果这个数据每个线程都自己保留自己的那一份,那么问题自然而然就解决了。而 ThreadLocal 就是干这个事的,每个线程都持有自己的变量,大家互不干涉,没有了同步问题,并发大大的上去了。

ThreadLocal 是附加在 Thread 上边的,在Thread源码中就存在这个代码。

  1. /*
  2. * ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
  3. * 一个Thread可以有很多个ThreadLocal,所以用了Map作为存储的数据结构
  4. */
  5. ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal是用于存储线程数据的,那么自然就有它的数据结构

数据结构

一个Thread可以有很多很多的ThreadLocal上下文,这些数据存储在 ThreadLocal.ThreadLocalMap 中, ThreadLocal 作为这个Map的key,这里的数据结构和HashMap是比较相似的。

  • HashMap: 数组+链表 (链表一定长度转红黑树)
  • ThreadLocalMap: 数组

数组的情况下,如果hash对应的下标被占用,就挪动到下一位。

读取

找到线程的ThreadLocalMap,获取hash对应的数组下标,如果下标不是目标数据,往下走,要么找到,要么返回null

写入

找到线程的ThreadLocalMap,获取hash对应的数组下标

  • 如果下标数据为null,写入
  • 当前key就是自己,替换value值,否则写入。
  • 找数组下一位数据,重复上述2个行为

    存在的问题

    ThreadLocal是绑定的线程上的,如果在当前线程new一个线程,那么想在子线程中获取到父线程的数据则不可能了。如果是线程池的情况下,线程都是复用的,像这种情况,数据又该如何从当前线程传递到子线程

  • InheritableThreadLoca: 解决父子线程传递数据的问题,在线程初始化的时候,会判断这里,如果有就拷贝这里的数据到子线程

  • TTL: 阿里巴巴开源可以解决当前线程的数据在线程池中获取的问题

    总结

从多线程方法共享内存中的变量说起,同时访问共享变量会有什么问题,JDK为了屏蔽硬件的差异性并同时解决这些问题有了JMM规范。规范描述了多线程访问共享数据之间,数据同步应该要怎么做。

JDK的ThreadLocal又可以解决那些问题,描述了其数据结构以及读取写入的过程。