1.特性分析
- 类功能
- 与类中其它普通变量的区别
- 普通的变量可以通过线程自身的get和set方法进行访问。
- 本地变量是独立初始化的变量de副本。
- ThreadLocal实例声明规则
-
定义为private static类型变量
- 和线程相关的状态(比如用户ID或者事务ID).
-
- 一个线程都会对线程本地变量持有一个明确引用de时间
- 线程处于alive状态
- ThreadLocal实例可以访问
note:线程消失后,它的本地变量实例的副本就成为GC的对象(除非有其它引用指向这些副本)
- 线程和ThreadLocal的关系
- 一个线程可以有多个ThreadLocal对象,线程使用ThreadLocal类对象的内部类ThreadLocalMap对其所有的ThreadLocal变量进行存储。
- 线程查找自己的某一个ThreadLocal对象时,使用Hash映射实现。
-
如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性
- 三个static变量保证
- private static AtomicInteger nextHashCode = new AtomicInteger();
- private static final int HASH_INCREMENT = 0x61c88647;
- private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
- nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用
- AtomicInteger保证了nextHashCode自增的原子性。
- 三个static变量保证
-
ThreadLocal内存泄漏问题
- ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用它,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链(如图红色箭头部分),永远无法回收,造成内存泄露。
- 解决措施
- ThreadLocal在一个线程中的初始化时间
方法变量分析">2.方法、变量分析
- threadLocalHashCode 说明
- 初始化值为null 如果编程人员期望本地变量有一个初始值而非null,则ThreadLocal必须被子类化,并且initialValue()这一方法被覆写 。
- public T get()
- public void set(T value)
- ThreadLocalMap类
- static class 类
- ThreadLocalMap是一个定制的哈希映射,只适用于维护线程本地值。
- 没有操作被导出ThreadLocal类.此类是包级私有的,允许在Thread类中声明的变量
- entry定义
- key:ThreadLocal
- value:ThreadLocal对应的value引用
- hash表的entry使用了弱引用类型的key 目的:为了辅助处理大且生命长的使用。
- 由于并未使用引用队列,只有当entry耗尽table的内存空间时才删除旧的条目.
- null值的key含义 此key不再被引用,也就意味着此entry可以从table中删除啦.这样的条目被称为“过时条目”。
- 初始化容量16
- 可以扩容的table,2倍扩容
- 负载因子:2/3
- 删除旧entry,解决了弱引用导致的内存溢出问题
涉及的方法
package sourcecode.analysis;
/**
* @Author: cxh
* @CreateTime: 18/5/14 17:26
* @ProjectName: JavaBaseTest
*/
import java.lang.*;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
/**
* 该类提供线程本地变量.这些变量和线程中其它普通的变量不同,因为那些普通的变量可以通过线程自身的get和set<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>进行访问.
* 而这些变量是独立初始化的变量副本.
* ThreadLocal实例通常是类中的private static类型变量,且和线程相关的状态(比如<a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>ID或者事务ID).
*
* 比如,下面的类会为每一个线程<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>一个唯一标识符.
* 在首次<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>ThreadId.get()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>时会<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>线程ID,且在后续的<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>中会保持不变.
*
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID,assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
*
* 只要线程还活着且ThreadLocal实例可以访问,则每一个线程都会对线程本地变量的副本持有一个明确引用.
* 线程消失后,它的本地变量实例的副本就成为GC的对象(除非有其它引用指向这些副本).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
/**
* ThreadLocal依赖于每个线程的线性探测hash映射.ThreadLocal和key一样,需要通过threadLocalHashCode进行查找.
* 这是一种<a href="/tag/zidingyi/" target="_blank" class="keywords">自定义</a>hash值(只在ThreadLocalMaps内有效),它消除了常见情况下由相同线程使用连续构造的ThreadLocals的冲突,
* 同时在不太常见的情况下也能保持良好的<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>。
*/
private final int threadLocalHashCode = nextHashCode();
//给出的下一个hash值;<a href="/tag/zidong/" target="_blank" class="keywords">自动</a>更新;开始值为0.
private static AtomicInteger nextHashCode =
new AtomicInteger();
//连续<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>的哈希码之间的差异-将隐式顺序本地线程ID转化为在大小为2的整数次幂table中几近完美的hash映射的值.
private static final int HASH_INCREMENT = 0x61c88647;
//返回下一个hash值
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 返回当前线程本地变量的"初始值".
* 这一<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>会在线程第一次通过get()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>访问变量时触发,除非线程之前已经<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>过set()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>,这种情况下本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>不会被触发.
* 通常,此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>每个线程最多触发一次,但是如果get(),remove()两个<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>相继被<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>,则此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>可能会在remove()后继续被触发一次.
*
* 这种实现简单地返回null;如果编程人员期望本地变量有一个初始值而非null,并且这一<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>被覆写.
* 通常,可以使用匿名内部类(感觉这句话应该是jdk8之前写的,从jdk8开始可以使用lambda表达式替代匿名内部类了)
*/
protected T initialValue() {
return null;
}
/**
* 使用本地变量创建一个线程.变量初始化值由Supplier<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>接口在<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>get<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>时决定.
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
//创建一个本地变量,无参实例构造器<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>
public ThreadLocal() {
}
//返回当前线程对此本地变量的副本的值.如果当前线程没有此变量的值,它会首先被初始化为由initialValue<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a><a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>的值.
public T get() {
java.lang.Thread t = java.lang.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;
}
}
return setInitialValue();
}
//变量的set()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>用于创建初始值.并未使用set()名字的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>是为了防止<a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>覆写set()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>.
private T setInitialValue() {
T value = initialValue();
java.lang.Thread t = java.lang.Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this,value);
else
createMap(t,value);
return value;
}
//将当前线程本地变量的副本设定为指定的值.大多数子类都没有必要覆写此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>,只是依赖于initialValue()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>对线程本地变量赋值即可.
public void set(T value) {
java.lang.Thread t = java.lang.Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this,value);
}
/**
* <a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>当前线程本地变量的值.如果本地变量随后会被当前线程读取,则它的值会通过initialValue重新初始化.这会带来在当前线程中多次<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>
* initialValue()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(java.lang.Thread.currentThread());
if (m != null)
m.remove(this);
}
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>和本地变量有关的map.在InheritableThreadLocal已经覆写了此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>
ThreadLocalMap getMap(java.lang.Thread t) {
return t.threadLocals;//threadLocals类型:ThreadLocal.ThreadLocalMap
}
//创建一个和ThreadLocal相关的map.在InheritableThreadLocal里面进行了覆写
void createMap(java.lang.Thread t,T firstValue) {
t.threadLocals = new ThreadLocalMap(this,firstValue);
}
//工厂<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>:用于创建继承类型的线程变量的map.
//本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>被设计目的:只是由Thread实例构造器<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>进行<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>.
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* childValue()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>在子类InheritableThreadLocal中是定义可见的.
* 此处被定义为内部<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的原因是:提供了createInheritedMap的工厂<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>但却没必要对InheritableThreadLocal中的map类进行子类化.
* 这种<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>比在<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>中嵌入测试实例更为可取。
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
//ThreadLocal的扩展类,它通过指定的<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>接口supplier<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>其初始化值
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
/**
* ThreadLocalMap是一个定制的哈希映射,只适用于维护线程本地值。没有操作被导出ThreadLocal类.此类是包级私有的,允许在Thread类中声明
* 变量.为了辅助处理大且生命长的使用,hash表的entry使用了弱引用类型的key.然而,由于并未使用引用队列,只有当entry耗尽table的内存空间时才
* <a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>旧的条目.
*/
static class ThreadLocalMap {
/**
* 此hashmap中的entry通过使用主引用字段作为key扩展了弱引用.
* 注意:null值的key表示此key不再被引用,也就意味着此entry可以从table中<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>啦.
* 这样的条目在下面的<a href="/tag/daima/" target="_blank" class="keywords">代码</a>中被称为“过时条目”。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k,Object v) {
super(k);
value = v;
}
}
//初始化容量16--必须是2的整数次幂
private static final int INITIAL_CAPACITY = 16;
//可以扩容的table.其长度必须为2的整数次幂
private Entry[] table;
//table中的条目数
private int size = 0;
//扩容时下一次的size大小,默认为0
private int threshold; // Default to 0
//设定扩容时的阈值以维持最差2/3的<a href="/tag/fuzai/" target="_blank" class="keywords">负载</a>因子
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
//返回当前下标i的下一个下标值,如果i==len-1,则从头开始,返回0
private static int nextIndex(int i,int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//返回当前下标的前一个下标值,如果i==0,则从尾部继续开始,返回len-1.
private static int prevIndex(int i,int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构建一个新的map,初始化包含(firstkey,firstvalue).
* ThreadLocalMaps是延迟构造的,因此当我们需要将entry放入map时才创建此map.
*/
ThreadLocalMap(ThreadLocal<?> firstKey,Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey,firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//创建一个新的map,它<a href="/tag/baokuo/" target="_blank" class="keywords">包括</a>了从给定父map继承的线程本地变量.此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>只被createInheritedMap()<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>.
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
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) {
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++;
}
}
}
}
/**
* <a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>和key相关的entry.此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>本身只处理fast path型映射:现有key的直接映射.否则会将处理逻辑转到getEntryAfterMiss()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>.
* 这一设计被用于最大限度提升直接命中的<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>.
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//直接映射可以找到,则返回
if (e != null && e.get() == key)
return e;
//直接映射找不到,则<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>其它<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的逻辑.
else
return getEntryAfterMiss(key,i,e);
}
/**
* getEntry()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的另一版本,用于在key直接映射找不到entry时
*
* @param key 线程本地对象
* @param i key的哈希值在table中的索引.
* @param e 在table[i]中的条目
* @return 和key相关的entry,如果没有则返回null
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key,int i,Entry e) {
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>当前table
Entry[] tab = table;
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>table长度
int len = tab.length;
//如果查找entry不为null
while (e != null) {
ThreadLocal<?> k = e.get();
//如果key相等,则返回结果
if (k == key)
return e;
//如果key为null,则<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>旧entry,<a href="/tag/jiejue/" target="_blank" class="keywords">解决</a>了弱引用导致的内存溢出问题
if (k == null)
expungeStaleEntry(i);
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>下一个索引i
else
i = nextIndex(i,len);
e = tab[i];
}
return null;
}
//设置和key相关的值
private void set(ThreadLocal<?> key,Object value) {
//此处,我们并未使用和get()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>一样的fast path方式,因为创建一个新的entry和替换一个已存在的entry基本相同,//在这种情况下,fail path的失败<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>要比不失败的<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>更多.
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>table
Entry[] tab = table;
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>表长
int len = tab.length;
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>key对应的索引位置
int i = key.threadLocalHashCode & (len-1);
//从当前索引开始查找
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i,len)]) {
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>当前本地变量
ThreadLocal<?> k = e.get();
//如果本地变量被找到,则更新值后返回.
if (k == key) {
e.value = value;
return;
}
//如果当前本地变量为null,则将此key和 value保存到table[i]中.
if (k == null) {
replaceStaleEntry(key,value,i);
return;
}
}
tab[i] = new Entry(key,value);
int sz = ++size;
if (!cleanSomeSlots(i,sz) && sz >= threshold)
rehash();
}
//手动<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>指定key的entry
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* 在使用指定的key进行set操作期间,如果key已经存在则用此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>进行替换操作.
* 通过参数传入的value会保存到entry中,无论指定key的entry是否已经存在.
*
* 本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的副作用就是:此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>会<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>包含旧条目的"run"中所有的条目(run的定义:table中两个空槽位之间的所有entry序列)
*
* @param staleSlot 查找key时遇到的第一个旧entry索引
*/
private void replaceStaleEntry(ThreadLocal<?> key,Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 备份以检查当前运行中的原始过期条目。
// 一次清理范围<a href="/tag/baokuo/" target="_blank" class="keywords">包括</a>整个允许阶段,这样做的目的是为了避免由于GC释放大量的引用而出现持续增长的rehash操作.
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
for (int i = nextIndex(staleSlot,len);
(e = tab[i]) != null;
i = nextIndex(i,len)) {
ThreadLocal<?> k = e.get();
//如果我们找到key,则我们需要将它和旧条目进行替换以维持hash表的顺序.
//新的旧槽位,或者其它任何旧的槽位如果遇到上述情况,则将被发送到expungeStaleEntry()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>而移除or
//重新rehash本次运行中所有的其它entry.
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;
}
//如果我们在向后扫描中没有找到过时的条目,在扫描key时看到的第一个旧条目是运行中第一个仍然存在的条目。
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果找不到此key对应的entry,那就在旧的槽位中放入一个新的entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key,value);
//如果运行中有其他过时的条目,则<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>它们。(这就是注释中说到的副作用)
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
}
/**
* 通过重置在旧槽位和下一个null槽位之间且可能发生冲突的entry来<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>一个旧的槽位.
* 且同时<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>了在null之前的所有旧entry.
*/
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)) {
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.
while (tab[h] != null)
h = nextIndex(h,len);
tab[h] = e;
}
}
}
return i;
}
/**
* 启发式扫描一些槽位寻找旧的条目。当<a href="/tag/tianjia/" target="_blank" class="keywords">添加</a>新元素或已<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>另一个元素时此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>被<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>.
* 本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>执行扫描的<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>的<a href="/tag/shuliang/" target="_blank" class="keywords">数量</a>级是对数级的,这是在无扫描(<a href="/tag/kuaisu/" target="_blank" class="keywords">快速</a>但保留<a href="/tag/laji/" target="_blank" class="keywords">垃圾</a>)和与元素<a href="/tag/shuliang/" target="_blank" class="keywords">数量</a>成正比的扫描<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>之间的平衡.
* 这将发现所有的<a href="/tag/laji/" target="_blank" class="keywords">垃圾</a>,但会导致一些插入操作占用O(n)的时间。
*
* @param i 索引为i的位置一定不包含旧条目.所以扫描操作从索引为i+1开始.
*
* @param n 扫描控制: log2(n)<a href="/tag/shuliang/" target="_blank" class="keywords">数量</a>级的槽位会被扫描,除非找到一个旧条目,在这种情况下,log2(table.length)-1个数的槽位
* 被扫描.当插入操作<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>时,此参数是元素的个数;但如果从replaceStaleEntry()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a><a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>时,此参数为表长.
* (注意:所有这些操作都会通过加权n而非logn进行或多或少的贪心方向的改变,但此版本的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>依旧简单,迅速,<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>良好.)
*
* @return 如果<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>了旧的entry则返回true
*/
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;
}
//重新包装和/或重新调整表的大小。首先扫描整个表,<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>过时的条目。如果这不能充分缩小表的大小,那么就要2被扩容表。
private void rehash() {
expungeStaleEntries();
//使用较低阈值进行2倍扩容以避免滞后现象
if (size >= threshold - threshold / 4)
resize();
}
//2倍扩容table的容量
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>旧table长度
int newLen = oldLen * 2;//2倍长度(新table长度)
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) //索引冲突时,重新<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>一个新索引
h = nextIndex(h,newLen);
newTab[h] = e;
count++;
}
}
}
//根据表的新长度,设定新阈值
setThreshold(newLen);
size = count;//size为table中元素个数
table = newTab;//原table引用指向新table
}
//<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>table中所有的旧entry
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);
}
}
}
}
package sourcecode.analysis;
/**
* @Author: cxh
* @CreateTime: 18/5/14 17:26
* @ProjectName: JavaBaseTest
*/
import java.lang.*;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
/**
* 该类提供线程本地变量.这些变量和线程中其它普通的变量不同,因为那些普通的变量可以通过线程自身的get和set<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>进行访问.
* 而这些变量是独立初始化的变量副本.
* ThreadLocal实例通常是类中的private static类型变量,且和线程相关的状态(比如<a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>ID或者事务ID).
*
* 比如,下面的类会为每一个线程<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>一个唯一标识符.
* 在首次<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>ThreadId.get()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>时会<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>线程ID,且在后续的<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>中会保持不变.
*
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID,assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
*
* 只要线程还活着且ThreadLocal实例可以访问,则每一个线程都会对线程本地变量的副本持有一个明确引用.
* 线程消失后,它的本地变量实例的副本就成为GC的对象(除非有其它引用指向这些副本).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
/**
* ThreadLocal依赖于每个线程的线性探测hash映射.ThreadLocal和key一样,需要通过threadLocalHashCode进行查找.
* 这是一种<a href="/tag/zidingyi/" target="_blank" class="keywords">自定义</a>hash值(只在ThreadLocalMaps内有效),它消除了常见情况下由相同线程使用连续构造的ThreadLocals的冲突,
* 同时在不太常见的情况下也能保持良好的<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>。
*/
private final int threadLocalHashCode = nextHashCode();
//给出的下一个hash值;<a href="/tag/zidong/" target="_blank" class="keywords">自动</a>更新;开始值为0.
private static AtomicInteger nextHashCode =
new AtomicInteger();
//连续<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>的哈希码之间的差异-将隐式顺序本地线程ID转化为在大小为2的整数次幂table中几近完美的hash映射的值.
private static final int HASH_INCREMENT = 0x61c88647;
//返回下一个hash值
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 返回当前线程本地变量的"初始值".
* 这一<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>会在线程第一次通过get()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>访问变量时触发,除非线程之前已经<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>过set()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>,这种情况下本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>不会被触发.
* 通常,此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>每个线程最多触发一次,但是如果get(),remove()两个<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>相继被<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>,则此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>可能会在remove()后继续被触发一次.
*
* 这种实现简单地返回null;如果编程人员期望本地变量有一个初始值而非null,并且这一<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>被覆写.
* 通常,可以使用匿名内部类(感觉这句话应该是jdk8之前写的,从jdk8开始可以使用lambda表达式替代匿名内部类了)
*/
protected T initialValue() {
return null;
}
/**
* 使用本地变量创建一个线程.变量初始化值由Supplier<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>接口在<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>get<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>时决定.
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
//创建一个本地变量,无参实例构造器<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>
public ThreadLocal() {
}
//返回当前线程对此本地变量的副本的值.如果当前线程没有此变量的值,它会首先被初始化为由initialValue<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a><a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>的值.
public T get() {
java.lang.Thread t = java.lang.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;
}
}
return setInitialValue();
}
//变量的set()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>用于创建初始值.并未使用set()名字的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>是为了防止<a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>覆写set()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>.
private T setInitialValue() {
T value = initialValue();
java.lang.Thread t = java.lang.Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this,value);
else
createMap(t,value);
return value;
}
//将当前线程本地变量的副本设定为指定的值.大多数子类都没有必要覆写此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>,只是依赖于initialValue()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>对线程本地变量赋值即可.
public void set(T value) {
java.lang.Thread t = java.lang.Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this,value);
}
/**
* <a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>当前线程本地变量的值.如果本地变量随后会被当前线程读取,则它的值会通过initialValue重新初始化.这会带来在当前线程中多次<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>
* initialValue()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(java.lang.Thread.currentThread());
if (m != null)
m.remove(this);
}
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>和本地变量有关的map.在InheritableThreadLocal已经覆写了此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>
ThreadLocalMap getMap(java.lang.Thread t) {
return t.threadLocals;//threadLocals类型:ThreadLocal.ThreadLocalMap
}
//创建一个和ThreadLocal相关的map.在InheritableThreadLocal里面进行了覆写
void createMap(java.lang.Thread t,T firstValue) {
t.threadLocals = new ThreadLocalMap(this,firstValue);
}
//工厂<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>:用于创建继承类型的线程变量的map.
//本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>被设计目的:只是由Thread实例构造器<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>进行<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>.
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* childValue()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>在子类InheritableThreadLocal中是定义可见的.
* 此处被定义为内部<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的原因是:提供了createInheritedMap的工厂<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>但却没必要对InheritableThreadLocal中的map类进行子类化.
* 这种<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>比在<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>中嵌入测试实例更为可取。
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
//ThreadLocal的扩展类,它通过指定的<a href="/tag/hanshu/" target="_blank" class="keywords">函数</a>接口supplier<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>其初始化值
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
/**
* ThreadLocalMap是一个定制的哈希映射,只适用于维护线程本地值。没有操作被导出ThreadLocal类.此类是包级私有的,允许在Thread类中声明
* 变量.为了辅助处理大且生命长的使用,hash表的entry使用了弱引用类型的key.然而,由于并未使用引用队列,只有当entry耗尽table的内存空间时才
* <a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>旧的条目.
*/
static class ThreadLocalMap {
/**
* 此hashmap中的entry通过使用主引用字段作为key扩展了弱引用.
* 注意:null值的key表示此key不再被引用,也就意味着此entry可以从table中<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>啦.
* 这样的条目在下面的<a href="/tag/daima/" target="_blank" class="keywords">代码</a>中被称为“过时条目”。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k,Object v) {
super(k);
value = v;
}
}
//初始化容量16--必须是2的整数次幂
private static final int INITIAL_CAPACITY = 16;
//可以扩容的table.其长度必须为2的整数次幂
private Entry[] table;
//table中的条目数
private int size = 0;
//扩容时下一次的size大小,默认为0
private int threshold; // Default to 0
//设定扩容时的阈值以维持最差2/3的<a href="/tag/fuzai/" target="_blank" class="keywords">负载</a>因子
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
//返回当前下标i的下一个下标值,如果i==len-1,则从头开始,返回0
private static int nextIndex(int i,int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//返回当前下标的前一个下标值,如果i==0,则从尾部继续开始,返回len-1.
private static int prevIndex(int i,int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构建一个新的map,初始化包含(firstkey,firstvalue).
* ThreadLocalMaps是延迟构造的,因此当我们需要将entry放入map时才创建此map.
*/
ThreadLocalMap(ThreadLocal<?> firstKey,Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey,firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//创建一个新的map,它<a href="/tag/baokuo/" target="_blank" class="keywords">包括</a>了从给定父map继承的线程本地变量.此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>只被createInheritedMap()<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>.
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
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) {
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++;
}
}
}
}
/**
* <a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>和key相关的entry.此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>本身只处理fast path型映射:现有key的直接映射.否则会将处理逻辑转到getEntryAfterMiss()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>.
* 这一设计被用于最大限度提升直接命中的<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>.
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//直接映射可以找到,则返回
if (e != null && e.get() == key)
return e;
//直接映射找不到,则<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>其它<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的逻辑.
else
return getEntryAfterMiss(key,i,e);
}
/**
* getEntry()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的另一版本,用于在key直接映射找不到entry时
*
* @param key 线程本地对象
* @param i key的哈希值在table中的索引.
* @param e 在table[i]中的条目
* @return 和key相关的entry,如果没有则返回null
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key,int i,Entry e) {
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>当前table
Entry[] tab = table;
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>table长度
int len = tab.length;
//如果查找entry不为null
while (e != null) {
ThreadLocal<?> k = e.get();
//如果key相等,则返回结果
if (k == key)
return e;
//如果key为null,则<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>旧entry,<a href="/tag/jiejue/" target="_blank" class="keywords">解决</a>了弱引用导致的内存溢出问题
if (k == null)
expungeStaleEntry(i);
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>下一个索引i
else
i = nextIndex(i,len);
e = tab[i];
}
return null;
}
//设置和key相关的值
private void set(ThreadLocal<?> key,Object value) {
//此处,我们并未使用和get()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>一样的fast path方式,因为创建一个新的entry和替换一个已存在的entry基本相同,//在这种情况下,fail path的失败<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>要比不失败的<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>更多.
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>table
Entry[] tab = table;
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>表长
int len = tab.length;
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>key对应的索引位置
int i = key.threadLocalHashCode & (len-1);
//从当前索引开始查找
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i,len)]) {
//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>当前本地变量
ThreadLocal<?> k = e.get();
//如果本地变量被找到,则更新值后返回.
if (k == key) {
e.value = value;
return;
}
//如果当前本地变量为null,则将此key和 value保存到table[i]中.
if (k == null) {
replaceStaleEntry(key,value,i);
return;
}
}
tab[i] = new Entry(key,value);
int sz = ++size;
if (!cleanSomeSlots(i,sz) && sz >= threshold)
rehash();
}
//手动<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>指定key的entry
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* 在使用指定的key进行set操作期间,如果key已经存在则用此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>进行替换操作.
* 通过参数传入的value会保存到entry中,无论指定key的entry是否已经存在.
*
* 本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>的副作用就是:此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>会<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>包含旧条目的"run"中所有的条目(run的定义:table中两个空槽位之间的所有entry序列)
*
* @param staleSlot 查找key时遇到的第一个旧entry索引
*/
private void replaceStaleEntry(ThreadLocal<?> key,Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 备份以检查当前运行中的原始过期条目。
// 一次清理范围<a href="/tag/baokuo/" target="_blank" class="keywords">包括</a>整个允许阶段,这样做的目的是为了避免由于GC释放大量的引用而出现持续增长的rehash操作.
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
for (int i = nextIndex(staleSlot,len);
(e = tab[i]) != null;
i = nextIndex(i,len)) {
ThreadLocal<?> k = e.get();
//如果我们找到key,则我们需要将它和旧条目进行替换以维持hash表的顺序.
//新的旧槽位,或者其它任何旧的槽位如果遇到上述情况,则将被发送到expungeStaleEntry()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>而移除or
//重新rehash本次运行中所有的其它entry.
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;
}
//如果我们在向后扫描中没有找到过时的条目,在扫描key时看到的第一个旧条目是运行中第一个仍然存在的条目。
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果找不到此key对应的entry,那就在旧的槽位中放入一个新的entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key,value);
//如果运行中有其他过时的条目,则<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>它们。(这就是注释中说到的副作用)
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
}
/**
* 通过重置在旧槽位和下一个null槽位之间且可能发生冲突的entry来<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>一个旧的槽位.
* 且同时<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>了在null之前的所有旧entry.
*/
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)) {
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.
while (tab[h] != null)
h = nextIndex(h,len);
tab[h] = e;
}
}
}
return i;
}
/**
* 启发式扫描一些槽位寻找旧的条目。当<a href="/tag/tianjia/" target="_blank" class="keywords">添加</a>新元素或已<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>另一个元素时此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>被<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>.
* 本<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>执行扫描的<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>的<a href="/tag/shuliang/" target="_blank" class="keywords">数量</a>级是对数级的,这是在无扫描(<a href="/tag/kuaisu/" target="_blank" class="keywords">快速</a>但保留<a href="/tag/laji/" target="_blank" class="keywords">垃圾</a>)和与元素<a href="/tag/shuliang/" target="_blank" class="keywords">数量</a>成正比的扫描<a href="/tag/cishu/" target="_blank" class="keywords">次数</a>之间的平衡.
* 这将发现所有的<a href="/tag/laji/" target="_blank" class="keywords">垃圾</a>,但会导致一些插入操作占用O(n)的时间。
*
* @param i 索引为i的位置一定不包含旧条目.所以扫描操作从索引为i+1开始.
*
* @param n 扫描控制: log2(n)<a href="/tag/shuliang/" target="_blank" class="keywords">数量</a>级的槽位会被扫描,除非找到一个旧条目,在这种情况下,log2(table.length)-1个数的槽位
* 被扫描.当插入操作<a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>此<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>时,此参数是元素的个数;但如果从replaceStaleEntry()<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a><a href="/tag/diaoyong/" target="_blank" class="keywords">调用</a>时,此参数为表长.
* (注意:所有这些操作都会通过加权n而非logn进行或多或少的贪心方向的改变,但此版本的<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a>依旧简单,迅速,<a href="/tag/xingneng/" target="_blank" class="keywords">性能</a>良好.)
*
* @return 如果<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>了旧的entry则返回true
*/
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;
}
//重新包装和/或重新调整表的大小。首先扫描整个表,<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>过时的条目。如果这不能充分缩小表的大小,那么就要2被扩容表。
private void rehash() {
expungeStaleEntries();
//使用较低阈值进行2倍扩容以避免滞后现象
if (size >= threshold - threshold / 4)
resize();
}
//2倍扩容table的容量
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;//<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>旧table长度
int newLen = oldLen * 2;//2倍长度(新table长度)
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) //索引冲突时,重新<a href="/tag/shengcheng/" target="_blank" class="keywords">生成</a>一个新索引
h = nextIndex(h,newLen);
newTab[h] = e;
count++;
}
}
}
//根据表的新长度,设定新阈值
setThreshold(newLen);
size = count;//size为table中元素个数
table = newTab;//原table引用指向新table
}
//<a href="/tag/shanchu/" target="_blank" class="keywords">删除</a>table中所有的旧entry
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);
}
}
}
}
参考: nofollow">https://www.cnblogs.com/coshaho/p/5127135.html