фоновый анализ
Я считаю, что многие программисты будут сталкиваться с определенными статическими переменными в процессе обычной реализации функций.Будь то однопоточная или многопоточная, они не будут влиять друг на друга, то есть на эту статическую переменную.Изоляция чтения-записи между потоки.
Существует служебный класс, который мы часто используем, его проблема параллелизма заключается в использовании ThreadLocal для решения, я уверен, что большинство людей видели, то естьSimpleDateFormat
Проблема многопоточности инструментов форматирования даты, если вы отправитесь в Интернет для поиска, должно быть много людей, которые говорят, что нужно использовать ThreadLocal.
определение
это чтоThreadLocal
Шерстяная ткань? Через наш китайский английский мы также можем перевести его, что означает локальный поток, и мы привыкли хранить переменные, которые нам нужны, чтобы иметь возможность изолировать потоки, то есть локальные переменные потока. То есть, когда мы сохраняем переменную в ThreadLocal, мы можем добиться изоляции потока этой переменной.
пример
Давайте сначала рассмотрим два примера, которые также включают два понятия, а именноПередача стоимостиипройти по ссылке.
- передать по значению
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
protected Integer initialValue(){
return 0;
}
};
// 值传递
@Test
public void testValue(){
for (int i = 0; i < 5; i++){
new Thread(() -> {
Integer temp = threadLocal.get();
threadLocal.set(temp + 5);
System.out.println("current thread is " + Thread.currentThread().getName() + " num is " + threadLocal.get());
}, "thread-" + i).start();
}
}
}
Вывод вышеуказанной программы:
current thread is thread-1 num is 5
current thread is thread-3 num is 5
current thread is thread-0 num is 5
current thread is thread-4 num is 5
current thread is thread-2 num is 5
Мы видим, что каждый поток печатает 5, даже если я пройду первымThreadLocal.get()
метод для получения переменной, а затемset
Заходите, все равно не будет повторной укладки.
Это изоляция потоков.
Но для передачи ссылки нам нужно уделить больше внимания, давайте посмотрим на пример напрямую.
- пройти по ссылке
public class ThreadLocalTest {
static NumIndex numIndex = new NumIndex();
private static ThreadLocal<NumIndex> threadLocal1 = new ThreadLocal<NumIndex>(){
protected NumIndex initialValue(){
return numIndex;
}
};
static class NumIndex{
int num = 0;
public void increment(){
num++;
}
}
// 引用传递
@Test
public void testReference(){
for (int i = 0; i < 5; i++){
new Thread(() -> {
NumIndex index = threadLocal1.get();
index.increment();
threadLocal1.set(index);
System.out.println("current thread is " + Thread.currentThread().getName() + " num is " + threadLocal1.get().num);
}, "thread-" + i).start();
}
}
}
Посмотрим на результат операции
current thread is thread-0 num is 2
current thread is thread-2 num is 3
current thread is thread-1 num is 2
current thread is thread-4 num is 5
current thread is thread-3 num is 4
Мы видим, что не только значения не изолированы, но и возникают проблемы с потокобезопасностью.
Поэтому мы должны обратить внимание на разницу между передачей по значению и передачей по ссылке, и мы не будем здесь говорить об этих двух концепциях.
Анализ исходного кода
Если вы хотите глубже понять роль ThreadLocal, вы должны вернуться к исходному коду и посмотреть, как два великих бога == Джош Блох и Дуг Ли == достигают этого? Весь класс составляет всего семь или восемьсот строк.
Здесь я разделяю его на две части, которыеThreadLocal
иThreadLocalMap
Анализ исходного кода этих двух.
Анализ исходного кода ThreadLocalMap
Подумав об этом снова и снова, я, наконец, решил поговорить первым.ThreadLocalMap
анализ исходного кода, зачем?
ThreadLocalMap
даThreadLocal
В нем есть статический внутренний класс, но это действительно очень критичная вещь.Так как мы смотрим на исходный код и хотим понять эту штуку, то у нас должно быть некое мышление, то есть если мы хотим реализовать такой функция, мы Как это сделать? А когда вы видите чужой код, вы должны научиться думать о том, почему другие это делают?
Я надеюсь, что в своей статье я не стремлюсь представить вам какие-то удивительные технологии, но, по крайней мере, дать вам понять, что нам нужно научиться строгой логике мышления этих больших коров.
Ближе к дому,ThreadLocalMap
Что именно? Мы должны думать об этом таким образом, поскольку это локальная переменная потока, и мы можем получать и присваивать значения через методы get и set.
1. В какой структуре хранится содержание нашего задания?
2. Как достигается изоляция потоков?
3. Когда я получаю и устанавливаю, как сохраняется соответствие значения потока?
Ответив на три вышеуказанных вопроса, а затем объединивThreadLocalMap
Это имя, я думаю, все знают, что это такое.
Да, этоThreadLocal
Самое основное содержимое — это класс, который поддерживает отношения между нашими потоками и переменными.Когда мы видим конец карты, мы также можем знать, что на самом деле это пара ключ-значение. Что же касается КЛЮЧА, мы увидим его в анализе исходного кода.
Вход во внутренний класс
Следующий исходный код извлечен из содержимого пояснительной части для отображения
static class ThreadLocalMap {
/**
* 自定义一个Entry类,并继承自弱引用
* 用来保存ThreadLocal和Value之间的对应关系
*
* 之所以用弱引用,是为了解决线程与ThreadLocal之间的强绑定关系
* 会导致如果线程没有被回收,则GC便一直无法回收这部分内容
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
* Entry数组的初始化大小
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
* <ThreadLocal, 保存的泛型值>数组
* 长度必须是2的N次幂
* 这个可以参考为什么HashMap里维护的数组也必须是2的N次幂
* 主要是为了减少碰撞,能够让保存的元素尽量的分散
* 关键代码还是hashcode & table.length - 1
*/
private Entry[] table;
/**
* The number of entries in the table.
* table里的元素个数
*/
private int size = 0;
/**
* The next size value at which to resize.
* 扩容的阈值
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
* 根据长度计算扩容的阈值
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 通过以下两个获取next和prev的代码可以看出,entry数组实际上是一个环形结构
*/
/**
* Increment i modulo len.
* 获取下一个索引,超出长度则返回0
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
* 返回上一个索引,如果-1为负数,返回长度-1的索引
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构造参数创建一个ThreadLocalMap代码
* ThreadLocal为key,我们的泛型为value
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化table的大小为16
table = new Entry[INITIAL_CAPACITY];
// 通过hashcode & (长度-1)的位运算,确定键值对的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建一个新节点保存在table当中
table[i] = new Entry(firstKey, firstValue);
// 设置table内元素为1
size = 1;
// 设置扩容阈值
setThreshold(INITIAL_CAPACITY);
}
/**
* ThreadLocal本身是线程隔离的,按道理是不会出现数据共享和传递的行为的
* 这是InheritableThreadLocal提供了了一种父子间数据共享的机制
* @param parentMap the map associated with parent thread.
*/
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++;
}
}
}
}
}
Для некоторых простых вещей, просто посмотрите на мои комментарии выше.
Мы видим, что во внутреннем классе ThreadLocalMap определен другой внутренний класс Entry, и он наследуется от слабых ссылок.Общим типом является ThreadLocal, у которого есть конструктор.Благодаря этому мы можем примерно предположить, что в ThreadLocalMap ключ фактически текущий объект ThreadLocal.
А зачем использовать слабые ссылки? Я думаю, что комментарии к моему исходному коду на самом деле очень ясны. Этот ThreadLocal на самом деле является классом инструментов для изоляции локальных переменных потока. Когда поток завершится, я определенно надеюсь переработать ресурсы, сгенерированные этой частью, поэтому я использую его. использованная литература.
Я полагаю, что у некоторых людей возникнут вопросы, что, если они будут переработаны, когда я их использую? Следующий код позволит вам шаг за шагом понять, что проблемы, которые вы рассмотрели, были продуманы и решены этими большими коровами. Тогда иди в школу!
методы getEntry и getEntryAfterMiss
Из названия метода видно, что он изThreadLocal
соответствующийThreadLocalMap
Получите нод Entry, тут надо подумать.
1) Что нам нужно для прохождения соответствующей Заявки?
2) Из вышеизложенного мы знаем, что используются слабые ссылки, что, если они возвращены сборщиком мусора, а не получены?
3) Если его нет в нижнем индексе, полученном расчетным путем, что делать?
4) ЕслиThreadLocal
соответствующийThreadLocalMap
Что если это не существует?
Вышеупомянутые 4 вопроса - это вещи, о которых я могу думать, когда смотрю на исходный код, а ответы на некоторые вопросы просто смотрят наTHreadLocalMap
Исходный код не очевиден,Его необходимо совместить с последующим анализом исходного кода ThreadLocal..
Давайте посмотрим, как исходный код Даниэля решает вышеуказанные проблемы.
/**
* 获取ThreadLocal的索引位置,通过下标索引获取内容
*/
private Entry getEntry(ThreadLocal<?> key) {
// 通过hashcode确定下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果找到则直接返回
if (e != null && e.get() == key)
return e;
else
// 找不到的话接着从i位置开始向后遍历,基于线性探测法,是有可能在i之后的位置找到的
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 循环向后遍历
while (e != null) {
// 获取节点对应的k
ThreadLocal<?> k = e.get();
// 相等则返回
if (k == key)
return e;
// 如果为null,触发一次连续段清理
if (k == null)
expungeStaleEntry(i);
// 获取下一个下标接着进行判断
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
Глядя на названия этих двух методов, мы знаем, что эти два метода являются методами получения узла Entry.
Мы сначала смотримgetEntry(ThreadLocal<?> key)
иgetEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)
Этот метод можно увидеть, непосредственно поThreadLocal
объект, поэтому мы можем снова доказать, что ключThreadLocal
объект, давайте посмотрим на его поток
1. Сначала определите индекс в таблице в соответствии с хэш-кодом ключа и table.length - 1
2. Если вы получаете его и возвращаете напрямую, но если вы его не получаете, то вернитесь назад, чтобы увидеть, можете ли вы его получить (поскольку используется метод линейного обнаружения, можно получить результат, пройдя назад )
3, введите метод GetEntryAfterMiss для линейного обнаружения, если приобретение возвращается напрямую; полученный ключ имеет значение NULL, запускает непрерывную очистку сегмента (на самом деле этот метод запускался разными способами, часто при непрерывной очистке сегмента, это ядро THREADLOCAL способ очистки).
метод expungeStaleEntry
Можно сказать, что этоThreadLocal
Очень важный метод очистки, зачем его очищать? Возможно, многие люди не понимают, мы не говорим, что нам нужно очищать содержимое, независимо от того, используем ли мы список или карту.
Но вот изолированная локальная переменная для потоков, и при использовании слабых ссылок она может быть переработана во время GC.
1) Если было переработано много узлов Entry, но в массиве таблиц еще остались места, ресурсы будут потрачены впустую, если их не очистить в это время
2) При очистке узлов можно пересчитывать индексы последующих непустых узлов Входа на выгрузку, чтобы можно было быстро находить ресурсы и ускорять эффективность при получении.
Давайте посмотрим, как это делает исходный код других людей!
/**
* 这个函数可以看做是ThreadLocal里的核心清理函数,它主要做的事情就是
* 1、从staleSlot开始,向后遍历将ThreadLocal对象被回收所在Entry节点的value和Entry节点本身设置null,方便GC,并且size自减1
* 2、并且会对非null的Entry节点进行rehash,只要不是在当前位置,就会将Entry挪到下一个为null的位置上
* 所以实际上是对从staleSlot开始做一个连续段的清理和rehash操作
*/
private int expungeStaleEntry(int staleSlot) {
// 新的引用指向table
Entry[] tab = table;
// 获取长度
int len = tab.length;
// expunge entry at staleSlot
// 先将传过来的下标置null
tab[staleSlot].value = null;
tab[staleSlot] = null;
// table的size-1
size--;
// Rehash until we encounter null
Entry e;
int i;
// 遍历删除指定节点所有后续节点当中,ThreadLocal被回收的节点
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 获取entry当中的key
ThreadLocal<?> k = e.get();
// 如果ThreadLocal为null,则将value以及数组下标所在位置设置null,方便GC
// 并且size-1
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else { // 如果不为null
// 重新计算key的下标
int h = k.threadLocalHashCode & (len - 1);
// 如果是当前位置则遍历下一个
// 不是当前位置,则重新从i开始找到下一个为null的坐标进行赋值
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;
}
Я считаю, что приведенные выше комментарии к коду были написаны очень ясно.Этот метод фактически начинается с staleSlot, чтобы выполнять непрерывную очистку сегмента и операцию повторного хеширования.
серия методов установки
Далее, давайте посмотрим на метод set, который, естественно, должен сохранять наши переменные вThreadLocal
Среди них он фактически сохраняется вThreadLocalMap
В середине, здесь мы должны подумать над несколькими вопросами.
1) еслиThreadLocal
соответствующийThreadLocalMap
Его еще нет, что делать?
2) Что делать, если рассчитанный индекс уже имеет узел Entry в таблице?
Я думаю, что благодаря объяснению приведенного выше кода у всех появилось больше идей по этим двум вопросам.
Старые правила, давайте посмотрим на реализацию кода дальше
/**
* ThreadLocalMap的set方法,这个方法还是挺关键的
* 通过这个方法,我们可以看出该哈希表是用线性探测法来解决冲突的
*/
private void set(ThreadLocal<?> key, Object value) {
// 新开一个引用指向table
Entry[] tab = table;
// 获取table的长度
int len = tab.length;
// 获取对应ThreadLocal在table当中的下标
int i = key.threadLocalHashCode & (len-1);
/**
* 从该下标开始循环遍历
* 1、如遇相同key,则直接替换value
* 2、如果该key已经被回收失效,则替换该失效的key
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
// 如果 k 为null,则替换当前失效的k所在Entry节点
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空的位置,创建Entry对象并插入
tab[i] = new Entry(key, value);
// table内元素size自增
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
// 新开一个引用指向table
Entry[] tab = table;
// 获取table的长度
int len = tab.length;
Entry e;
// 记录当前失效的节点下标
int slotToExpunge = staleSlot;
/**
* 通过这个for循环的prevIndex(staleSlot, len)可以看出
* 这是由staleSlot下标开始向前扫描
* 查找并记录最前位置value为null的下标
*/
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
/**
* 通过for循环nextIndex(staleSlot, len)可以看出
* 这是由staleSlot下标开始向后扫描
*/
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 获取Entry节点对应的ThreadLocal对象
ThreadLocal<?> k = e.get();
/**
* 如果与新的key对应,直接赋值value
* 则直接替换i与staleSlot两个下标
*/
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
// 通过注释看出,i之前的节点里,没有value为null的情况
if (slotToExpunge == staleSlot)
slotToExpunge = i;
/**
* 在调用cleanSomeSlots进行启发式清理之前
* 会先调用expungeStaleEntry方法从slotToExpunge到table下标所在为null的连续段进行一次清理
* 返回值便是table[]为null的下标
* 然后以该下标--len进行一次启发式清理
* 最终里面的方法实际上还是调用了expungeStaleEntry
* 可以看出expungeStaleEntry方法是ThreadLocal核心的清理函数
*/
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
/**
* 如果当前下标所在已经失效,并且向后扫描过程当中没有找到失效的Entry节点
* 则slotToExpunge赋值为当前位置
*/
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
// 如果并没有在table当中找到该key,则直接在当前位置new一个Entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
/**
* 在上面的for循环探测过程当中
* 如果发现任何无效的Entry节点,则slotToExpunge会被重新赋值
* 就会触发连续段清理和启发式清理
*/
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* 启发式地清理被回收的Entry
* i对应的Entry是非无效的,有可能是失效被回收了,也有可能是null
* 会有两个地方调用到这个方法
* 1、set方法,在判断是否需要resize之前,会清理并rehash一遍
* 2、替换失效的节点时候,也会进行一次清理
*/
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];
// Entry对象不为空,但是ThreadLocal这个key已经为null
if (e != null && e.get() == null) {
n = len;
removed = true;
/**
* 调用该方法进行回收
* 实际上不是只回收 i 这一个节点而已
* 而是对 i 开始到table所在下标为null的范围内,对那些节点都进行一次清理和rehash
*/
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* 对table进行扩容,因为要保证table的长度是2的幂,所以扩容就扩大2倍
*/
private void resize() {
// 获取旧table的长度,并且创建一个长度为旧长度2倍的Entry数组
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
// 记录插入的有效Entry节点数
int count = 0;
/**
* 从下标0开始,逐个向后遍历插入到新的table当中
* 1、如遇到key已经为null,则value设置null,方便GC回收
* 2、通过hashcode & len - 1计算下标,如果该位置已经有Entry数组,则通过线性探测向后探测插入
*/
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
size = count;
// 指向新的Entry数组
table = newTab;
}
Приведенный выше код вызывает метод set дляThreadLocalMap
Среди серии кодов, чтобы сохранить отношения K-V, я не говорю отдельно друг от друга, так что каждый смотрит оценку также удобнее, есть преемственность.
Мы можем взглянуть на весь процесс установки:
1. Сначала используйте хэш-код & (len - 1), чтобы найтиThreadLocal
индекс в таблице
2. Цикл for перемещается в обратном направлении
1) Если мы получим ключ узла Вход и что нам нужно для работыThreadLocal
Если равно, замените значение напрямую
2) Если при обходе ключ равен нулю, вызовитеreplaceStaleEntry
Метод заменить его.
3. Если два вышеуказанных условия верны, вставьте новую стадию входа непосредственно в вычисляемые индексы.
4. Выполните эвристическую очистку и, если размер вставленного узла превышает порог расширения, вызовите метод изменения размера для расширения.
метод удаления
Поскольку он хранится в виде карты, у нас есть метод put, поэтому должно быть время удаления, и любая структура данных должна соответствовать добавлению, удалению, модификации и проверке.
Перейдем непосредственно к коду.
/**
* Remove the entry for key.
* 将ThreadLocal对象对应的Entry节点从table当中删除
*/
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) {
// 将引用设置null,方便GC
e.clear();
// 从该位置开始进行一次连续段清理
expungeStaleEntry(i);
return;
}
}
}
Мы можем видеть, удалить время узла, будет использовать линейное зондирование, и когда найти соответствующий ключ, когда вызов будет ясной ссылкой на нуль, и вызовет непрерывную очистку сегмента.
Я считаю, что вышеизложенноеThreadLocalMap
Анализ исходного кода ThreadLocal дал всем базовое концептуальное представление о ней Я считаю, что когда все понимают концепцию ThreadLocal, это не просто знать, что нужно реализовать локальные переменные потока.
Тогда давайте посмотримThreadLocal
анализ исходного кода.
Анализ исходного кода ThreadLocal
ThreadLocal
С исходным кодом все гораздо проще, потому что внутренний класс в основном работает с ThreadLocalMap, управляя нашими локальными переменными.
Получить серию методов
/**
* 获取当前线程本地变量的值
*/
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果map不为空
if (map != null) {
// 如果当前ThreadLocal对象对应的Entry还存在
ThreadLocalMap.Entry e = map.getEntry(this);
// 并且Entry不为null,返回对应的值,否则都执行setInitialValue方法
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果该线程对应的ThreadLocalMap还不存在,则执行初始化方法
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
// 获取初始值,一般是子类重写
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果map不为null
if (map != null)
// 调用ThreadLocalMap的set方法进行赋值
map.set(this, value);
// 否则创建个ThreadLocalMap进行赋值
else
createMap(t, value);
return value;
}
/**
* 构造参数创建一个ThreadLocalMap代码
* ThreadLocal为key,我们的泛型为value
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化table的大小为16
table = new Entry[INITIAL_CAPACITY];
// 通过hashcode & (长度-1)的位运算,确定键值对的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建一个新节点保存在table当中
table[i] = new Entry(firstKey, firstValue);
// 设置table内元素为1
size = 1;
// 设置扩容阈值
setThreshold(INITIAL_CAPACITY);
}
ThreadLocal
Метод get не сложный, всего несколько строк кода, но когда он комбинируетThreadLocalMap
После метода вся эта логика стоит того, чтобы вникнуть в мышление человека, написавшего этот инструмент.
Давайте взглянем на один из его процессов.
1. Получить текущий поток и получить соответствующий поток в соответствии с текущим потокомThreadLocalMap
2. ВThreadLocalMap
Среди полученияThreadLocal
Узел Entry, соответствующий объекту, и возвращает соответствующее значение
3. Если полученоThreadLocalMap
Если оно равно null, это доказывает, что оно не было инициализировано, и вызывается метод setInitialValue.
1) При вызове метода setInitialValue будет гарантировано двойное получение, а потом снова получитьThreadLocalMap
2) Если он по-прежнему равен нулю, наконец вызывается конструктор ThreadLocalMap.
серия методов установки
Я здесь не дляThreadLocal
Метод set был введен слишком много, в сочетании с вышеуказаннымThreadLocalMap
Метод множества, я думаю, что могу дать общий ответ на вопросы, возникающие в связи с каждым из вышеперечисленных методов.
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程所对应的ThreadLocalMap,从这可以看出每个线程都是独立的
ThreadLocalMap map = getMap(t);
// 如果map不为空,则k-v赋值,看出k是this,也就是当前ThreaLocal对象
if (map != null)
map.set(this, value);
// 如果获取的map为空,则创建一个并保存k-v关系
else
createMap(t, value);
}
фактическиThreadLocal
Метод set очень прост, главное вызватьThreadLocalMap
Метод set, который является реальным основным процессом выполнения.
Но давайте посмотрим на процесс:
1, текущий полученный поток, соответствующий текущему полученному потокуThreadLocalMap
2. Если соответствующийThreadLocalMap
Если он не нулевой, вызовите его метод set, чтобы сохранить соответствующее отношение.
3. Если карта нулевая, она, наконец, будет вызванаThreadLocalMap
Конструктор создаетThreadLocalMap
и сохранить переписку
Сводка процесса выполнения
Сводка анализа кода исходного кода
сверху через паруThreadLocal
иThreadLocalMap
Исходный код двух классов был проанализирован, думаю, у всех есть четкое понимание всего процесса работы функции ThreadLocal. Я действительно восхищаюсь ==Джошем Блохом и Дугом Леа== этими двумя великими богами, когда они реализуют эту вещь, они не имеют в виду, что этого достаточно, чтобы это реализовать, но рассматривают множество ситуаций, таких как: проблемы GC, как поддерживать его хорошо Вопрос хранения данных и того, как должно быть установлено соответствие между потоками и локальными переменными.
Логика кода, который они написали, очень строгая, просмотрев сотни строк кода, мы действительно обнаружили, что находимся не в основном в техническом отставании от других, а во всем наборе мыслительной логики реализации функций. между ними огромная пропасть.Самый очевидный момент в том, что мы реализуем его чисто для реализации, и принципиально не рассматриваем другие нештатные ситуации, не говоря уже о каких-то проблемах GC.
Таким образом, благодаря анализу исходного кода я действительно понял, что мы не можем просто смотреть на исходный код для перевода, мы должны узнать, как они думают о реализации такой функции, и мы должны узнать, как они думают о логике каждая функция.