简单了解ThreadLocal

什么是ThreadLocal?

ThreadLocal是线程变量,每个线程可以在一个ThreadLocal里面存放一个变量,这个变量是线程安全的,除了ThreadLocal还可以用栈的本地变量或者锁来保证线程安全,并且可以用于方法间的数据传递。

ThreadLocalMap是ThreadLocal的一个内部类,用于保存数据,key是ThreadLocal的一个弱引用(可以有很多个ThreadLocal),那如果是强引用就不会被回收了,value是存放的变量值

 // 强引用
 ThreadLocal<Object> threadLocal = new ThreadLocal<>();
 threadLocal.get();
 // 弱引用
 new ThreadLocal<>().get();

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有自己的ThreadLocalMap

简单来说就是每个线程一个ThreadLocalMap,存放很多个 <不同的ThreadLocal,不同的变量值>

应用场景

保存某个信息在线程的任意时刻可以使用,如保存用户user信息,可以获取userId或者其他什么信息

源码解析

hash冲突

和HashMap的链地址法不同,ThreadLocal采用nextIndex()和prevIndex()往前后找位置

nextIndex()和prevIndex()

向后或者向前遍历

 private static int nextIndex(int i, int len) {
     return ((i + 1 < len) ? i + 1 : 0);
 }
 ​
 private static int prevIndex(int i, int len) {
     return ((i - 1 >= 0) ? i - 1 : len - 1);
 }

set()

  1. 计算下标位置

  2. 下标位置就是空白的,则直接加入

  3. 下标位置有节点,则往后遍历,如果发现key相同的节点,进行值替换

  4. 如果没有过期节点,一直找到空白位置

  5. 如果有过期节点,进行替换,并且进行清理

 public void set(T value) {
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null) {
         map.set(this, value);
     } else {
         createMap(t, value);
     }
 }
 ​
 private void set(ThreadLocal<?> key, Object value) {
 ​
     Entry[] tab = table;
     int len = tab.length;
     // 对于同一个线程的下标位置都会相同 
     int i = key.threadLocalHashCode & (len-1);
     // 找到合适的位置存放新节点
     for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
         ThreadLocal<?> k = e.get();
         // key相同替换
         if (k == key) {
             e.value = value;
             return;
         }
         // 有节点,但是节点的key为null,说明节点被回收了,替换之
         if (k == null) {
             replaceStaleEntry(key, value, i);
             return;
         }
     }
     // 找到了空白的位置,准备加入新节点
     tab[i] = new Entry(key, value);
     int sz = ++size;
     // 超过阈值进行扩容
     if (!cleanSomeSlots(i, sz) && sz >= threshold)
         rehash();
 }

replaceStaleEntry()

过期节点替换和垃圾清理(往前找到空白前的第一个过期节点作为开始清理点)

slotToExpunge:从这个位置开始垃圾清理

staleSlot:需要替换的过期节点位置

 private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                int staleSlot) {
     Entry[] tab = table;
     int len = tab.length;
     Entry e;
     // 遍历检测还有没有其它过期的元素,直到null,用slotToExpunge记录开始点,之后用于清理数据
     int slotToExpunge = staleSlot;
     for (int i = prevIndex(staleSlot, len);
          (e = tab[i]) != null;
          i = prevIndex(i, len))
         if (e.get() == null)
             // 当前节点过期了,更新slotToExpunge
             slotToExpunge = i;
 ​
     // 从staleSlot往后遍历
     for (int i = nextIndex(staleSlot, len);
          (e = tab[i]) != null;
          i = nextIndex(i, len)) {
         ThreadLocal<?> k = e.get();
 ​
         // 这里主要是为了配合调用者方法,也有一个key相同的处理方式
         if (k == key) {
             e.value = value;
             
             tab[i] = tab[staleSlot];
             tab[staleSlot] = e;
 ​
             // 相等的意思是,staleSlot前边没有过期节点,从当前位置开始垃圾清理
             if (slotToExpunge == staleSlot)
                 slotToExpunge = i;
             cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
             return;
         }
 ​
         // 更新slotToExpunge
         if (k == null && slotToExpunge == staleSlot)
             slotToExpunge = i;
     }
 ​
     // 替换staleSlot位置为新节点,也是这个方法的主要目的
     tab[staleSlot].value = null;
     tab[staleSlot] = new Entry(key, value);
 ​
     // 相等的意思是,staleSlot前边没有过期节点,从当前位置开始垃圾清理
     if (slotToExpunge != staleSlot)
         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
 }

垃圾清理

expungeStaleEntry()

线性清理,遇到空白则停止清理,这里的staleSlot实际上是传进来的slotToExpunge

 
private int expungeStaleEntry(int staleSlot) {
     Entry[] tab = table;
     int len = tab.length;
 ​
     // expunge entry at staleSlot
     tab[staleSlot].value = null;
     tab[staleSlot] = null;
     size--;
 ​
     // Rehash until we encounter null
     Entry e;
     int i;
     // 往后遍历
     for (i = nextIndex(staleSlot, len);
          (e = tab[i]) != null;
          i = nextIndex(i, len)) {
         ThreadLocal<?> k = e.get();
         // 直接清理
         if (k == null) {
             e.value = null;
             tab[i] = null;
             size--;
         } else {
             // 进行rehash重新存放位置
             int h = k.threadLocalHashCode & (len - 1);
             if (h != i) {
                 tab[i] = null;
 ​
                 // Unlike Knuth 6.4 Algorithm R, we must scan until
                 // null because multiple entries could have been stale.
                 while (tab[h] != null)
                     h = nextIndex(h, len);
                 tab[h] = e;
             }
         }
     }
     return i;
 }

cleanSomeSlots()

每次循环n/=2,n是table[]数组的长度,直到n==0停止清理,也是线性清理

 private boolean cleanSomeSlots(int i, int n) {
     boolean removed = false;
     Entry[] tab = table;
     int len = tab.length;
     do {
         i = nextIndex(i, len);
         Entry e = tab[i];
         if (e != null && e.get() == null) {
             n = len;
             removed = true;
             i = expungeStaleEntry(i);
         }
     } while ( (n >>>= 1) != 0);
     return removed;
 }

扩容

 // threshold 默认 2/3 * len
 if (!cleanSomeSlots(i, sz) && sz >= threshold)
     rehash();
 ​
 private void rehash() {
     // 从开头开始清理所有过期节点,然后移动零散节点到一起
     expungeStaleEntries();
 ​
     // 因为有些过期节点被清理,减少扩容的阈值判断
     if (size >= threshold - threshold / 4)
         // 2倍扩容,重新rehash
         resize();
 }

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值