前言
月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂)
央是一片海洋,海乃百川,代表着一块海绵(吸纳万物)
泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出)
月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容
希望大家一起坚持这个过程,也同样希望大家最终都能从零到零,把知识从薄变厚,再由厚变薄!
一.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).
* 1.此类提供线程局部变量。这些变量与它们的普通对应变量不同,因为每个访问一个的线程(通过其get或set方法)都有自己的、
* 独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
* <pre>
* 2.例如,下面的类生成每个线程本地的唯一标识符。
* 线程的 id 在它第一次调用ThreadId.get()时被分配,并且在后续调用中保持不变
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* //包含要分配的下一个线程 ID 的原子整数
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* //包含每个线程 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
* //返回当前线程的唯一 ID,必要时分配它
* public static int get() {
* return threadId.get();
* }
* }
* </pre>
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
* 3.只要线程处于活动状态并且ThreadLocal实例是可访问的,每个线程都持有对其线程局部变量副本的隐式引用;
* 线程消失后,它的所有线程本地实例副本都将进行垃圾回收(除非存在对这些副本的其他引用)。
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
二.成员变量:
/**
* ThreadLocals 依赖于附加到每个线程(Thread.threadLocals 和inheritableThreadLocals)
* 的每线程线性探针哈希映射。 ThreadLocal 对象充当键,通过 threadLocalHashCode 进行搜索。
* 这是一个自定义哈希代码(仅在 ThreadLocalMaps 中有用),它消除了在相同线程使用连续构造的
* ThreadLocals 的常见情况下的冲突,同时在不太常见的情况下保持良好的行为
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 要给出的下一个哈希码。原子更新。从零开始
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* 连续生成的哈希码之间的差异 - 将隐式顺序线程本地 ID 转换为接近最优分布的乘法哈希值,用于 2 次方大小的表
*/
private static final int HASH_INCREMENT = 0x61c88647;
三.内部类:
suppliedThreadLocal
/**
* ThreadLocal 的扩展,它从指定的Supplier获取其初始值。
*/
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
/**
* ThreadLocalMap 是一种定制的哈希映射,仅适用于维护线程本地值。
* 不会在 ThreadLocal 类之外导出任何操作。该类是包私有的,以允许在类 Thread 中声明字段。
* 为了帮助处理非常大且长期存在的用法,哈希表条目使用 WeakReferences 作为键。
* 但是,由于不使用引用队列,因此只有在表开始耗尽空间时才能保证删除陈旧条目
*/
static class ThreadLocalMap {
/**
* 这个哈希映射中的条目扩展了 WeakReference,
* 使用它的主要 ref 字段作为键(它总是一个 ThreadLocal 对象)。
* 请注意,空键(即 entry.get() == null)意味着不再引用该键,因此可以从表中删除该条目。
* 此类条目在以下代码中称为“陈旧条目”
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
//与此 ThreadLocal 关联的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量 -- 必须是 2 的幂
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 表格,根据需要调整大小。 table.length 必须始终是 2 的幂
*/
private Entry[] table;
/**
* 表中的条目数。
*/
private int size = 0;
/**
* 要调整大小的下一个大小值。
*/
private int threshold; // Default to 0
/**
* 置调整大小阈值以在最坏情况下保持 2/3 负载因子
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 下一个索引
*/
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);
}
/**
* 构造一个最初包含 (firstKey, firstValue) 的新映射。
* ThreadLocalMaps 是惰性构造的,所以我们只有在至少有一个条目可以放入时才创建一个
*/
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);
}
/**
* 从给定的父映射构造一个包含所有可继承线程本地的新映射。仅由 createInheritedMap 调用。
*/
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++;
}
}
}
}
/**
* 获取与密钥关联的条目。此方法本身仅处理快速路径:直接点击现有密钥。
* 否则它会中继到 getEntryAfterMiss。这旨在最大限度地提高直接命中的性能,
* 部分原因是使该方法易于内联
*/
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
return getEntryAfterMiss(key, i, e);
}
/**
* 当在其直接散列槽中找不到密钥时使用的 getEntry 方法的版本
*/
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* 设置与键关联的值
*/
private void set(ThreadLocal<?> key, Object value) {
//我们不像 get() 那样使用快速路径,因为使用 set()
// 创建新条目至少与替换现有条目一样常见,在这种情况下,快速路径会经常失败.
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();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
*/
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;
}
}
}
/**
* 1.将设置操作期间遇到的陈旧条目替换为指定键的条目。在 value 参数中传递的值存储在条目中,无论指定键的条目是否已经存在。
*2.作为副作用,此方法会清除包含陈旧条目的“运行”中的所有陈旧条目。 (运行是两个空槽之间的一系列条目。)
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//备份以检查当前运行中先前的陈旧条目。我们一次清除整个运行,
// 以避免由于垃圾收集器成束地释放引用(即,每当收集器运行时)而导致的持续增量重新散列。
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//找到 run 的键或尾随空槽,以先发生者为准
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//如果我们找到键,那么我们需要将它与陈旧的条目交换以维护哈希表顺序。
// 然后可以将新的陈旧槽或在其上方遇到的任何其他陈旧槽发送到 expungeStaleEntry
// 以删除或重新散列运行中的所有其他条目。
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//如果存在,则从前面的陈旧条目开始清除
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//如果我们在向后扫描中没有找到陈旧条目,那么在扫描密钥时看到的第一个陈旧条目就是运行中仍然存在的第一个条目
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果未找到密钥,则将新条目放入陈旧的插槽中
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//如果运行中有任何其他陈旧条目,请删除它们
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* 通过重新散列位于 staleSlot 和下一个空槽之间的任何可能冲突的条目来清除陈旧的条目。
* 这也会清除在尾随空值之前遇到的任何其他陈旧条目。见 Knuth,第 6.4 节
* 返回值:staleSlot 之后的下一个空槽的索引(所有在 staleSlot 和这个槽之间的都将被检查以进行清除)
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
//在 staleSlot 删除条目
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
//重新哈希直到我们遇到 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 {
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.
//与 Knuth 6.4 算法 R 不同,我们必须扫描直到为空,因为多个条目可能已经过时。
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
/**
* 启发式扫描一些单元格以查找过时的条目。当添加新元素或删除另一个陈旧元素时会调用此方法。
* 它执行对数扫描,作为不扫描(快速但保留垃圾)和扫描次数与元素数量成正比之间的平衡,
* 这将找到所有垃圾但会导致某些插入花费 O(n) 时间。
* 1.一个已知不会持有过时条目的位置。扫描从 i 之后的元素开始
* 2.log2(n)单元格被扫描,除非找到一个陈旧的条目,在这种情况下log2(table.length)-1额外的单元格被扫描。从插入中调用时,
* 此参数是元素数,但从replaceStaleEntry 中调用时,它是表长度。
* (注意:所有这些都可以通过对 n 进行加权而不是仅使用直接对数 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;
}
/**
* 重新打包和/或重新调整表格的大小。首先扫描整个表,删除陈旧的条目。
* 如果这不能充分缩小表格的大小,请将表格大小加倍
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
//使用较低的加倍阈值以避免滞后
if (size >= threshold - threshold / 4)
resize();
}
/**
* Double the capacity of the table.
* 桌子的容量翻倍。
*/
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);
}
}
}
四.构造方法:
/**
* 创建线程局部变量。
*/
public ThreadLocal() {
}
五.内部方法:
withInitial
/**
* 创建线程局部变量。变量的初始值是通过调用Supplier上的get方法确定的
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
get
/**
*返回此线程局部变量的当前线程副本中的值。如果变量没有当前线程的值,
* 它首先被初始化为调用 initialValue方法返回的值。
*/
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;
}
}
return setInitialValue();
}
set
/**
* 将此线程局部变量的当前线程副本设置为指定值。大多数子类将不需要覆盖此方法,
* 仅依靠 initialValue方法来设置线程局部变量的值。.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove
/**
* 1.删除此线程局部变量的当前线程值。如果此线程局部变量随后被当前线程get read,
* 则其值将通过调用其initialValue方法重新初始化,除非它的值由当前线程中的当前线程set set临时。
* 这可能会导致在当前线程中多次调用 initialValue方法。
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
六.总结:
ThreadLocal作为一种隔离手段,可以说在某些时刻非常好用...