ThreadLocal源码解读

本文深入剖析了Java中的ThreadLocal类,详细解释了其内部的斐波那契散列法用于提高哈希分布均匀性,以及在set、get和扩容过程中的实现细节,包括线程局部变量的存储、查找、替换和清理策略。同时,讨论了ThreadLocalMap的内存管理和性能优化措施。
摘要由CSDN通过智能技术生成

new

在new ThreadLocal的时候,采用了斐波那锲散列法生成对应的hash使得hash值分布的更加平均

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

set

public void set(T value) {
  //从本线程拿到ThreadLocalMap
  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) {

  // We don't use a fast path as with get() because it is at
  // least as common to use set() to create new entries as
  // it is to replace existing ones, in which case, a fast
  // path would fail more often than not.

  Entry[] tab = table;
  int len = tab.length;
  //算key值
  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;
    }
		//如果k已经被回收,因为为弱引用(看手写笔记)
  	//注意,有可能在后面还有k == key的情况,要在replaceStaleEntry中判断
    if (k == null) {
      //替换这个key,
      replaceStaleEntry(key, value, i);
      return;
    }
  }
	
  //说明找到了为null的,插入
  tab[i] = new Entry(key, value);
  int sz = ++size;
  //先清理一下tab,看是否要扩容,如果大于了阀值(注意一般超过tab的3/4就要扩容,所以插入的时候是不会遇到满表的情况)
  //如果清理返回为true,说明清理了一些槽,不需要扩容
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
    //扩容
    rehash();
}

replaceStaleEntry,探测式清理

//入参为待插入的key,待插入的value,staleSlot这个要替换的槽
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
  Entry[] tab = table;
  int len = tab.length;
  Entry e;

  // Back up to check for prior stale entry in current run.
  // We clean out whole runs at a time to avoid continual
  // incremental rehashing due to garbage collector freeing
  // up refs in bunches (i.e., whenever the collector runs).
  //先看看在这个槽之前有没有已经被垃圾回收过的,用slotToExpunge记录一下离前面最远的(不为null)且被垃圾回收过的元素。
  int slotToExpunge = staleSlot;
  for (int i = prevIndex(staleSlot, len);
       (e = tab[i]) != null;
       i = prevIndex(i, len))
    if (e.get() == null)
      slotToExpunge = i;

  // Find either the key or trailing null slot of run, whichever
  // occurs first
  //如果一直到为null的槽当中有key和待插入的key一样将其移位到这个槽
  for (int i = nextIndex(staleSlot, len);
       (e = tab[i]) != null;
       i = nextIndex(i, len)) {
    ThreadLocal<?> k = e.get();

    // If we find key, then we need to swap it
    // with the stale entry to maintain hash table order.
    // The newly stale slot, or any other stale slot
    // encountered above it, can then be sent to expungeStaleEntry
    // to remove or rehash all of the other entries in run.
    //如果后面找到了key,将其移动到这个要替换的槽(为null)上
    if (k == key) {
      e.value = value;

      tab[i] = tab[staleSlot];
      tab[staleSlot] = e;

      // Start expunge at preceding stale entry if it exists
      //如果前面也没有发现要清理的槽,就从当前的槽开始清理
      if (slotToExpunge == staleSlot)
        slotToExpunge = i;
      //清理槽,包括探测式清理
      cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
      return;
    }

    // If we didn't find stale entry on backward scan, the
    // first stale entry seen while scanning for key is the
    // first still present in the run.
    if (k == null && slotToExpunge == staleSlot)
      slotToExpunge = i;
  }

  // If key not found, put new entry in stale slot
  tab[staleSlot].value = null;
  tab[staleSlot] = new Entry(key, value);

  // If there are any other stale entries in run, expunge them
  if (slotToExpunge != staleSlot)
    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

expungeStaleEntry

从这个槽之后到为null的槽为止清除掉被垃圾回收的槽

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 {
      //如果不为null,说明还没有被回收,还存在引用
      //此时判断一下这个key是本来就在这个槽上还是因为原来槽上有东西被移位了的,如果被移位了,再次重新计算一下槽,将其放到离key计算的槽最近的槽上去。
      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;
      }
    }
  }
  //返回的就是为null的槽的编号
  return i;
}

cleanSomeSlots

//入参为null值的槽,以及tab的长度
//试探的扫描一些单元格,寻找过期 元素,也就是被垃圾回收的元素。当添加新元素或删除另一个过时元素时,将调 用此函数。它执行对数扫描次数,作为不扫描(快速但保留垃圾)和与元素数量 成比例的扫描次数之间的平衡,这将找到所有垃圾,但会导致一些插入花费 O(n) 时间。
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;
}

rehash()

扩容

private void rehash() {
  //先全盘清理一下
  expungeStaleEntries();
	//超过了tab的3/4就要扩容
  // Use lower threshold for doubling to avoid hysteresis
  if (size >= threshold - threshold / 4)
    resize();
}

//全盘扫描清除
private void expungeStaleEntries() {
  Entry[] tab = table;
  int len = tab.length;
  for (int j = 0; j < len; j++) {
    Entry e = tab[j];
    if (e != null && e.get() == null)
      expungeStaleEntry(j);
  }
}

resize

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
  //扩容为原来的两倍
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;
		//全部重新计算放到哪个槽里
    for (Entry e : oldTab) {
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}

get

threadLocal.get();

public T get() {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    //取值
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
      @SuppressWarnings("unchecked")
      T result = (T)e.value;
      return result;
    }
  }
  //如果没有map,说明还没有初始化
  return setInitialValue();
}

map.getEntry(this);

private Entry getEntry(ThreadLocal<?> key) {
  int i = key.threadLocalHashCode & (table.length - 1);
  Entry e = table[i];
  if (e != null && e.get() == key)
    return e;
  else
    //如果e.get()!=key说明要在表的这个槽后续找找
    return getEntryAfterMiss(key, i, e);
}

getEntryAfterMiss

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  Entry[] tab = table;
  int len = tab.length;

  while (e != null) {
    ThreadLocal<?> k = e.get();
    //如果找到了直接返回
    if (k == key)
      return e;
    //碰到了有被垃圾回收的槽,但是value还没有被垃圾回收,就采用探测式清理。
    if (k == null)
      expungeStaleEntry(i);
    else
      i = nextIndex(i, len);
    e = tab[i];
  }
  //如果从hash值算的那个槽往后找到null的槽的时候还是没有找到,那么就说明没有
  return null;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值