本文结构

  • ThreadLocal简介 (简要说明ThreadLocal的作用)
  • ThreadLocal实现原理(说明ThreadLocal的常用方法和原理)
  • ThreadLocalMap的实现 (说明核心数据结构ThreadLocalMap的实现)

ThreadLocal简介


先贴一段官方的文档解释

/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/

大意是ThreadLocal类提供了一个线程的本地变量,每一个线程持有一个这个变量的副本并且每个线程读取get()到的值是不一样的,可以通过set()方法设置这个值;

在某些情况下使用ThreadLocal可以避免共享资源的竞争,同时与不影响线程的隔离性

通过threadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,

这样每个线程使用自己保存的ThreadLocalMap对象,不会影响线程之间的隔离。

看到这里的第一眼我一直以为ThreadLocal是一个map,每一个线程都是一个key,对应一个value,但是是不正确的。正确的是每个线程持有一个ThreadLocalMap的副本,这个map的键是ThreadLocal对象,各个线程中同一key对应的值可以不一样。

ThreadLocal实现原理


ThreadLocal中的字段与构造方法

详细说明参考注释

public class ThreadLocal<T> {

   //当前ThreadLocal对象的HashCode值,
//通过这个值可以定位Entry对象在ThreadLocalMap中的位置
//由nextHashCode计算得出
private final int threadLocalHashCode = nextHashCode(); //一个自动更新的AtomicInteger值,官方解释是会自动更新,怎么更新的不知道,
//看完AtomicInteger源码回来填坑
private static AtomicInteger nextHashCode =
new AtomicInteger(); //ThreadLocal的魔数
//0x61c88647是斐波那契散列乘数,它的优点是通过它散列(hash)出来的结果分布会比较均匀,可以很大程度上避免hash冲突,
private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() {
//原子操作:将给定的两个值相加
return nextHashCode.getAndAdd(HASH_INCREMENT);
} /**
* 返回当前线程变量的初始值
* 这个方法仅在没有调用set方法的时候第一次调用get方法调用
*/
protected T initialValue() {
return null;
}
//构造方法
public ThreadLocal() {
} //创建ThreadLocalMap,
//当前的ThreadLocal对象和value加入map当中
//赋值给当前线程的threadLocals字段
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
.......
}

常用方法get()、set()、remove

get()方法

get()方法的源码,具体的代码解释请看注释

//返回当前线程中保存的与当前ThreadLocal相关的线程变量的值(有点绕,可以看代码注释)
public T get() {
Thread t = Thread.currentThread(); //返回当前线程的threadLocals字段的值,类型是ThreadLocalMap
//暂时可以将ThreadLocalMap当作HashMap,下文解释
ThreadLocalMap map = getMap(t);
if (map != null) { //Entry也可以按照HashMap的entry理解
//Entry保存了两个值,一个值是key,一个值是value
//返回当前ThreadLocalMap中当前ThreadLcoal对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; //返回Entry中对应的值
return result;
}
}
//如果当前线程的ThreadLocalMap不存在,则构造一个
return setInitialValue();
}

getMap(Thread t) 方法

//返回当前线程的threadLocals字段的值,类型为ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

getEntry(ThreadLocal<?> key)方法

//table是一个数组,具体的可以看下文的ThreadLocalMap解释
//返回当前ThreadLocalMap中key对应的Entry private Entry getEntry(ThreadLocal<?> key) {
//根据key值计算所属Entry所在的索引位置(同HashMap) int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//由于存在散列冲突,判断当前节点是否是对应的key的节点
if (e != null && e.get() == key)
//返回这个节点
return e;
else
return getEntryAfterMiss(key, i, e);
}

setInitialValue()方法

 private T setInitialValue() {

     //在构造方法部分有写,返回一个初始值,
//默认情况(没有被子类重写)下是一个null值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//再次判断当前线程中threadLocals字段是否为空值
if (map != null)
//set可以看作HashMap中的put
map.set(this, value);
else
//当map为null时
//构造一个ThreadLocalMap,
//并以自身为键,initialValue()的结果为值插入ThreadLocalMap
//并赋值给当前线程的threadLocals字段
createMap(t, value);
return value;
}

createMap方法

void createMap(Thread t, T firstValue) {
//调用THreadLocalMap的构造方法
//构造一个ThreadLocalMap,
//使用给定的值构造一个存储的实例(Entry的对象)存储到map中
//并保存到thread的threadLocals字段中
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

        从get()方法中大概可以看出ThreadLocalMap是一个以ThreadLocal对象为键一个Map,并且这个ThreadLocalMap对象由Thread类维护,并保存在threadLocals字段中,不同的ThreadLocal对象可以以自身为键访问这个Map中对应位置的值。

        当第一次调用get()(之前没有调用过set()或者调用了remove())时,会调`initialValue()添加当前ThreadLocal对象对应的值为null并返回。

set()方法

set()方法的源码,具体的代码的解释请看注释

/**
* 设定当前线程的线程变量的副本为指定值
* 子类一般不用重写这个方法
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
//返回当前线程的threadLocals字段的值,类型是ThreadLocalMap,同get()方法
ThreadLocalMap map = getMap(t);
//同get()一样 判断当前线程的threadLocals字段的是否为null
if (map != null)
//不为null,设置当前ThreadLocal(key)对应的值(value)为指定的value
map.set(this, value);
else
//null,创建ThreadLocalMap对象,将[t, value]加入map,并赋值给当前线程的localThreads字段
createMap(t, value);
}

remove()方法

remove()方法的源码,具体代码的解释请看注释

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//从当前线程的threadLocals字段中移除当前ThreadLocal对象为键的键值对
//remove方法在 ThreadLocalMap中实现
m.remove(this);
}

从上面的实现代码看,get()、set()、remove这三个方法都是很简单的从一个ThreadLocalMap中获取设置或者移除值,那么有一个核心就是ThreadLocalMap,那么下面就分析下ThreadLocalMap

ThreadLocalMap的实现


个人觉得replaceStaleEntry()expungeStaleEntry()cleanSomeSlots()这三个方法是ThreadLocal中非常重要难以理解的方法;

/**
* ThreadLocalMap 是一个定制的哈希散列映射,仅仅用用来维护线程本地变量
* 对其的所有操作都在ThreadLocal类里面。
* 使用软引用作为这个哈希表的key值(软引用引用的对象在强引用解除引用后的下一次GC会被释放)
* 由于不使用引用队列,表里的数据只有在表空间不足时才会被释放
* (因为使用的时key-value,在key被释放·null·后这个表对应的位置不会变为null,需要手动释放)
* 这个map和HashMap不同的地方是,
* 在发生哈希冲突的时候HashMap会使用链表(jdk8之后也可能是红黑树)存储(拉链法)
* 而这里使用的是向后索引为null的表项来存储(开放地址法)
*/
static class ThreadLocalMap { /**
* 这个hash Map的条目继承了 WeakReference, 使用他的ref字段作为key(一个ThreadLocal对象)
* ThreadLocal object). 注意当键值为null时代表整个键已经不再被引用(ThreadLocal
* 对象已经被垃圾回收)因此可以删除对应的条目
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
//key值是当前TreadLocal对象的弱引用
super(k);
value = v;
}
} //类的属性字段,基本和HashMap作用一致 //初始容量,必须是2的幂
private static final int INITIAL_CAPACITY = 16; //哈希表
//长度必须是2的幂,必要时会调整大小
private Entry[] table; //表中存储数据的个数
private int size = 0; //当表中数据的个数达到这个值时需要扩容
private int threshold; // Default to 0 //设置threshold ,负载系数时2/3,也就是说当前表中数据的条目
//达到表总容量的2/3就需要扩容
private void setThreshold(int len) {
threshold = len * 2 / 3;
} /**
* Increment i modulo len.
*/
//这个注释不明白,但是在代码实现中遍历表的的时候用来判断是否到了表的结尾
//如果到了表节位就从表首接着这遍历
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
} /**
* Decrement i modulo len.
*/
//这个注释不明白,但是在代码实现中遍历表的的时候用来判断是否到了表的头部
//如果到了表节位就从表尾接着这遍历
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
} //构造一个新map包含 (firstKey, firstValue).
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//构造表
table = new Entry[INITIAL_CAPACITY];
//确定需要加入的数据的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
构造一个新的Entry对象并放入到表的对应位置
table[i] = new Entry(firstKey, firstValue);
//设置当前表中的数据个数
size = 1;
// 设置需要扩容的临界点
setThreshold(INITIAL_CAPACITY);
} //这个方法在创建一个新线程调用到Thread.init()方法是会被调用
//目的是将父线程的inheritableThreadLocals传递给子线程
//创建的map会被存储在Thread.inheritableThreadLocals中
//根据parentMap构造一个新的ThreadLocalMap,
//这个map包含了所有parentMap的值
//只有在createInheritedMap调用
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
//根据parentMap的长度(容量)构造table
table = new Entry[len]; //依次复制parentMap中的数据
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) { //childValue在InheritableThreadLocal中实现
//也只有InheritableThreadLocal对象会调用这个方法
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1); //解决哈希冲突的办法是向后索引
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}

getEntry()获取指定key对应的Entry对象

    /**
* 通过key获取key-value对. 这个方法本身只处理快速路径(直接命中)
* 如果没有命中继续前进(getEntryAfterMiss)
* 这是为了使命中性能最大化设计
*/
private Entry getEntry(ThreadLocal<?> key) {
//计算key应该出现在表中的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//判断获取到的entry的key是否是给出的key
if (e != null && e.get() == key)
//是就返回entry
return e;
else
//否则向后查找,
return getEntryAfterMiss(key, i, e);
} /**
* getEntry的派生,当直接哈希的槽里找不到键的时候使用
*
* @param key the thread local object
* @param i key在table中哈希的结果
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; //根绝ThreadLocalMapc插入的方法,插入时通过哈希计算出来的槽位不为null
//则向后索引,找到一个空位放置需要插入的值
//所以从哈希计算的槽位到插入值的位置中间一定是不为null的
//因为e!=null可以作为循环终止条件
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
//如果命中则返回
return e;
if (k == null)
//e!=null && k==null,证明对应的ThreadLcoal对象已经被释放
//那么这个位置的entry就可以被释放
//释放位置i上的空间
//释放空间也是ThreadLocalMap与HashMap不相同的地方
expungeStaleEntry(i);
else
//获取下一个查找的表的索引下标
//当i>=len时会从0号位重新开始查找
i = nextIndex(i, len);
e = tab[i];
}
//没找到返回null
return null;
}

set()修改或者创建指定的key对应的Entry对象


//添加一个key-value对
private void set(ThreadLocal<?> key, Object value) { // 不像get一样使用快速路径,
// set创建新条目和修改现有条目一样常见
// 这种情况下快速路径通常会失败 Entry[] tab = table;
int len = tab.length;
//计算应该插入的槽的位置
int i = key.threadLocalHashCode & (len-1); //哈希计算的槽位到插入值的位置中间一定是不为null的
//该位置是否位null可以作为循环终止条件
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //修改现有的键值对的值
if (k == key) {
e.value = value;
return;
} //e!=null && k==null,证明对应的ThreadLcoal对象已经被释放
//那么这个位置的entry就可以被释放
//释放位置i上的空间
//释放空间也是ThreadLocalMap与HashMap不相同的地方
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} //在可以插入值的地方插入
tab[i] = new Entry(key, value);
int sz = ++size;
//清除部分k是null的槽然后判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//扩容
rehash();
}

remove()移除指定key对应的Entry对象

    /**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//同set()
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//弱引用引用置空,标记这个槽已经是旧槽
e.clear();
//清理旧槽
expungeStaleEntry(i);
return;
}
}
}

set()过程中处理旧槽的核心方法——replaceStaleEntry()


/**
* Replace a stale entry encountered during a set operation
* with an entry for the specified key. The value passed in
* the value parameter is stored in the entry, whether or not
* an entry already exists for the specified key.
*
* As a side effect, this method expunges all stale entries in the
* "run" containing the stale entry. (A run is a sequence of entries
* between two null slots.)
*
* @param key the key
* @param value the value to be associated with key
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
//run:两个空槽中间所有的非空槽
//姑且翻译成运行区间
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e; // 备份检查当前运行中的以前的陈旧条目.
// 我们每次清理整个运行区间,避免垃圾收集器一次释放过多的引用
// 而导致增量的哈希 // slotToExpunge删除的槽的起始位置,因为在后面清除(expungeStaleEntry)
// 的过程中会扫描从当前位置到第一个空槽之间的位置,所以这里只需要判断出
// 扫描的开始位置就可以
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
//从传进来的旧槽的位置往后查找key值或者第一个空槽结束
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get(); // 如果在staleSlot位置的槽后面找到了指定的key,那么将他和staleSlot位置的槽进行交换
// 以保持哈希表的顺序
// 然后将新的旧槽护着上面遇到的任何过期的槽通过expungeStaleEntry删除
// 或者重新哈希所有运行区间的其他条目 //找到了对应的key,将他和staleSlot位置的槽进行交换
if (k == key) {
e.value = value; tab[i] = tab[staleSlot];
tab[staleSlot] = e; // 判断清理旧槽的开始位置
// 如果在将staleSlot之前没有旧槽,那么就从当前位置为起点清理
if (slotToExpunge == staleSlot)
slotToExpunge = i; //expungeStaleEntry清理从给定位置开始到第一个null的区间的空槽
//并返回第一个null槽的位置p
//cleanSomeSlots从expungeStaleEntry返回的位置p开始清理log(len)次表 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); //将给定的key-value已经存放,也清理了相应运行区间 ,返回
return;
} // 如果在向后查找后没有找到对应的key值,而当前的槽是旧槽
// 同时如果在向前查找中也没查找到旧槽
// 那么进行槽清理的开始位置就是当前位置
//为什么不是staleSlot呢?因为在这个位置创建指定的key-value存放
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
} // 如果在向后查找后没有找到对应的key值,在staleSlot位置创建该key-value
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value); // 确定扫描过程中发现过空槽
if (slotToExpunge != staleSlot)
//清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

replaceStaleEntry()计算了很久的扫描清理的起点位置,总结下应该分为四种情况:

  • prev方向上没有旧槽,在next方向上找到key值之前没有找到旧槽,那么就交换keystaleSlot然后从当前位置向后清理空槽
  • prev方向上没有旧槽,在next方向上没有找到key没有找到旧槽,那么在staleSlot位置创建指定的key-value
  • prev方向上没有旧槽,在next方向上没有找到key但是找到旧槽,那么在staleSlot位置创建指定的key-value,并从找到的第一个旧槽的位置开始清理旧槽
  • prev方向上找到旧槽,在next方向上没有找到key,那么在staleSlot位置创建指定的key-value,从prev方向最后一个找到的旧槽开始清理旧槽
  • prev方向上找到旧槽,在next方向上找到key,那么就交换keystaleSlot,从prev方向最后一个找到的旧槽开始清理旧槽

求推荐个好看的画图工具

清理旧槽的核心方法——expungeStaleEntry()cleanSomeSlots()

    /**
* 删除指定位置上的旧条目,并扫描从当前位置开始到第一个发现的空位之间的陈旧条目
* 还会删除尾随的第一个null之前的所有旧条目
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length; // 释放槽
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; // 重新哈希直到遇到null
Entry e;
int i;
//第staleSlot槽已经空出来,
//从下一个槽开始扫描旧条目直到遇到空槽
//因为整个表只适用2/3的空间,所以必然会遇到空槽
//删除扫描期间遇到的空槽
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 {
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.
//寻找到从第h位开始的第一个空槽放置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//返回扫描过程中遇到的第一个空槽
return i;
} /**
* Heuristically scan some cells looking for stale entries.
* This is invoked when either a new element is added, or
* another stale one has been expunged. It performs a
* logarithmic number of scans, as a balance between no
* scanning (fast but retains garbage) and a number of scans
* proportional to number of elements, that would find all
* garbage but would cause some insertions to take O(n) time.
*
* @param i a position known NOT to hold a stale entry. The
* scan starts at the element after i.
*
* @param n scan control: {@code log2(n)} cells are scanned,
* unless a stale entry is found, in which case
* {@code log2(table.length)-1} additional cells are scanned.
* When called from insertions, this parameter is the number
* of elements, but when from replaceStaleEntry, it is the
* table length. (Note: all this could be changed to be either
* more or less aggressive by weighting n instead of just
* using straight log n. But this version is simple, fast, and
* seems to work well.)
*
* @return true if any stale entries have been removed.
*/
//扫描一部分表清理其中的就条目,
//扫描log(n)个
private boolean cleanSomeSlots(int i, int n) {
//在这次清理过程中是否清理了部分槽
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
//从i的下一个开始查找(第i个在调用这个方法前已经查找)
i = nextIndex(i, len);
Entry e = tab[i];
//key=e.get(),e不为null,但key为null
if (e != null && e.get() == null) {
n = len;
//标记清理过某个槽
removed = true;
//清理过程
i = expungeStaleEntry(i);
}
//只扫描log(n)个槽,一方面可以保证避免过多的空槽的堆积
//一方面可以保证插入或者删除的效率
//因为删除的时候会扫描两个空槽之前的槽位
//每个槽位全部扫描的话时间复杂度会高,
//因为在expungeStaleEntry扫描当前位置到第一个空槽之间所有的旧槽
//所以在这里进行每个槽位的扫描会做很多重复的事情,效率低下
//虽然扫描从i开始的log(n)也会有很多重复扫描,但是通过优良的哈希算法
//可以减少哈希冲突也就可以减少重复扫描的数量
} while ( (n >>>= 1) != 0);
return removed;
} /**
* Re-pack and/or re-size the table. First scan the entire
* table removing stale entries. If this doesn't sufficiently
* shrink the size of the table, double the table size.
*/
private void rehash() {
expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
} //扩容至当前表的两倍容量
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0; for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
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;
} /**
* Expunge all stale entries in the table.
*/
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);
}
}
}

一些问题


  • 为什么使用弱引用:

    因为使用弱引用不会影响ThreadLocal对象被释放后的垃圾回收,由于使用了弱引用,

    被释放的对象只能存活到下一次gc,对象被回收后弱引用就变为null,这时候就可以进行判断这个位置的条目是否已经是旧条目,

    从而进行清理。防止内存泄漏,

    尽管软引用的对象也可以被垃圾回收掉,但是对象会存活到内存不足这样会造成内存泄漏
  • 如何解决旧的槽中Entry对象不被回收造成内存泄漏问题:在get()中碰到旧槽会通过expungeStaleEntry()方法来清理旧槽,

    清理完成后会继续向后清理直到遇到第一个空槽,在此期间会进行哈希重定位操作,

    将每个槽中的都西昂放在合适的位置以维持哈希表的顺序;

    set()方法中调用replaceStaleEntry-->expungeStaleEntry()清理整个运行区间内的旧槽,

    并调用cleanSomeSlots()循环扫描log(n)个槽位进行清理,使用优秀的哈希算法减少每次调用到expungeStaleEntry()重复清理同一区间的工作;

\(\color{#FF3030}{转载请标明出处}\)

ThreadLocal原理分析的更多相关文章

  1. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  2. ThreadLocal原理分析与使用场景

    什么是ThreadLocal变量 ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本.这里有几点需要注意: 因为每个 Thr ...

  3. ThreadLocal原理分析与代码验证

    ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...

  4. ThreadLocal 原理分析

    用法 ThreadLocal<String> threadLocal = new ThreadLocal<>(); // 无初始值 ThreadLocal<String& ...

  5. ThreadLocal原理及其实际应用

    前言 java猿在面试中,经常会被问到1个问题: java实现同步有哪几种方式? 大家一般都会回答使用synchronized, 那么还有其他方式吗? 答案是肯定的, 另外一种方式也就是本文要说的Th ...

  6. Handler系列之原理分析

    上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...

  7. [转]Handler MessageQueue Looper消息循环原理分析

    Handler MessageQueue Looper消息循环原理分析   Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...

  8. Junit 注解 类加载器 .动态代理 jdbc 连接池 DButils 事务 Arraylist Linklist hashset 异常 哈希表的数据结构,存储过程 Map Object String Stringbufere File类 文件过滤器_原理分析 flush方法和close方法 序列号冲突问题

    Junit 注解 3).其它注意事项: 1).@Test运行的方法,不能有形参: 2).@Test运行的方法,不能有返回值: 3).@Test运行的方法,不能是静态方法: 4).在一个类中,可以同时定 ...

  9. Eventbus 使用方法和原理分析

    对于 Eventbus ,相信很多 Android 小伙伴都用到过. 1.创建事件实体类 所谓的事件实体类,就是传递的事件,一个组件向另一个组件发送的信息可以储存在一个类中,该类就是一个事件,会被 E ...

随机推荐

  1. Codeforces Round #509 (Div. 2) A. Heist 贪心

    There was an electronic store heist last night. All keyboards which were in the store yesterday were ...

  2. CF思维联系–CodeForces-217C C. Formurosa(这题鸽了)

    ACM思维题训练集合 The Bytelandian Institute for Biological Research (BIBR) is investigating the properties ...

  3. 图论--SCC缩点--Tarjan

    // Tarjan算法求有向图强连通分量并缩点 /*强连通缩点与双连通缩点大同小异,也就是说将强连通分支缩成一个点之后,没有强连通,成为有向无环图,在对图进行题目的操作.*/ #include< ...

  4. POJ - 3074 Sudoku (搜索)剪枝+位运算优化

    In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For exa ...

  5. python(运算符)

    一.运算符 1.算数运算符 (1)加(+) 注意:字符串与整数之间不能进行相加,需要通过str()或int()进行转换数据类型 整数与整数相加 >>> 1 + 1 2 >> ...

  6. andorid jar/库源码解析之okhttp3

    目录:andorid jar/库源码解析 Okhttp3: 作用: 用于网络编程(http,https)的快速开发. 栗子: // okHttpClient定义成全局静态,或者单例,不然重复new可能 ...

  7. ssm整合后打印日志查看执行sql语句

    mybatis.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configura ...

  8. Centos7 使用 Ansible 批量安装中文字体

    需求背景 Centos7 下 Java 生成图片水印时中文乱码,原因是没有安装中文字体. 安装中文字体 以下是基于 Centos7 手动安装中文字体的详细步骤.当测试或者生产环境服务器比较多的时候,建 ...

  9. CF-292D Connected Components 并查集 好题

    D. Connected Components 题意 现在有n个点,m条编号为1-m的无向边,给出k个询问,每个询问给出区间[l,r],让输出删除标号为l-r的边后还有几个连通块? 思路 去除编号为[ ...

  10. 花店橱窗布置问题(FLOWER)

    目录 问题描述 问题分析 Java代码实现 运行结果 今天老师上完课说所有花都要被放,这个算法还是考虑多了,包含了这个选择,代码就不给了,用dp思想就可以解决了. 问题描述   假设你想以最美观的方式 ...