jdk源码01-ThreadLocal

ThreadLocal简介

在多线程访问同一个变量场景下,我们可以使用加锁(synchronized)以解决线程安全问题,而ThreadLocal为我们提供了一种全新的思路。与它绑定的thread各自维护一份共享变量,所以共享变量的线程安全问题得以解决。下面我们通过源码看看它是如何做到的以及它存在的问题。

ThreadLocal类

在idea上可以看到,ThreadLocal的类结构如下,我们一一介绍

  • 内部类:
    • SuppliedThreadLocal:为ThreadLocal初始化value值
    • ThreadLocalMap:后面介绍
  • 变量:
    • HASH_INCREMENT:一个16进制整数值
    • nextHashCode:与HASH_INCREMENT可以获取较为均匀的哈希码,并且是原子性的
    • threadLocalHashCode
  • 方法:
    • ThreadLocal()
    • childValue():某些场景下需要将父线程的变量传递给子线程,可以使用InhroitableThreadLocal完成。在这里是提供一种警示
    • createInheritedMap():创建本地线程的映射
    • createMap():创建一个ThreadLocalMap
    • get():获取map中的value
    • getMap():获取map
    • initialValue():默认初始化null
    • nextHashCode()
    • remove():移除map中的key
    • set():设值map中的value
    • setInitialValue():设值初始化value
    • withInitial()

ThreadLocal重点介绍

在介绍ThreadLocal中的几个主要方法之前,我们有必要弄清楚ThreadLocal,Thread以及ThreadLocalMap三者之间的关系。按照我的理解Thread和本地value作为键值对存储与ThreadLocalMap中,而ThreadLocal维护这个map。参考了许多大神的博客以及视频之后,我发现我的理解是有误的。其实看源码应该知道,ThreadLocalMap一方面是ThreadLocal的内部类,另一方面是Thread的成员变量。而该map的键值对分别为ThreadLocal和共享变量。

  • set()源码:
    • 首先获取当前线程
    • 获取当前线程绑定的ThreadLocalMap结构
    • 如果map不为null,调用内部类ThreadLocalMap中的set方法(后文介绍)
    • 如果map为null则调用createMap创建新的map
      • 进一步跟踪createMap,里面是new ThreadLocalMap(),目的一目了然了
  • get()源码
    • 如果map不为null,调用getEntry方法
      • 进入getEntry方法,首先获取下标i位置的entry e
      • 如果e为null或者e的key值不等于当前k,则调用getEntryAfterMiss方法(后文介绍)
    • 如果map为null,则调用setInitialVlaue方法,里面代码简单

ThreadLocalMap介绍

在看源码中,其实ThreadLocal的set/get方法都是调用的内部类ThreadLocalMap的set/get方法,下面分析一下ThreadLocalMap。

  • 首先声明了一个Entry内部类,构造函数的两个参数分别为ThreadLocal类型的k,且为弱引用;以及Object的v
  • 它的成员变量有Entry类型的数组table,初始容量大小为16,数组扩容阈值为当用到2/3时,数组的下标nextIndex,prevIndex
    • 这里的prevIndex存在,是因为Entry数组被视为环形,所以当nextIndex为最后一个位置时,则需要取到数组的第一个位置
  • set()源码
    • 在多线程环境下,每个线程进入到这个set()时,都会通过下面这段代码获取table数组的下标i
      int i = key.threadLocalHashCode & (len-1);

       

    • 然后以该i值为起点,遍历整个table,目的是寻找是否有以当前线程为k的元素。如果寻找到这个k,则直接设值

    • 在遍历代码块中,有判断k==null,出现这个的原因是我们前面介绍的弱引用,它的特点是随时会被垃圾回收
    • 如果遍历中没有找到相关的key,则新建一个entry放入table数组中

ThreadLocal其它

  • 为什么Entry设置为环形?
    • 因为需要使用线性探测来解决哈希冲突
  • 为什么ThreadLocal设置为弱引用类型?
    • 在多线程场景下,如果key使用强引用,则有可能造成内存泄露。在使用弱引用时,由于key会随时被回收,则减少了内存泄露的可能性,但是依然有内存泄露的可能性。为什么呢?虽然key会被回收,但是大量的value依然存在!
    • 如果想完全避免内存泄漏,需要使用remove方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值