Оригинальный адрес этой статьи:Блог jsbintask(Лучший съедобный эффект), просьба указывать источник для перепечатки!
предисловие
ThreadLocal
Это очень важный инструмент в jdk. Он может контролировать доступ к объектам в куче памяти только для определенных потоков. Если вы часто читаете исходный код, вы можете найти его следы в основных фреймворках. И его самое классическое применение事务管理
, а также частый гость в интервью.
принцип
Мы знаем, что память кучи является общей, почему ThreadLocal может контролировать доступ указанного потока? Как показано на рисунке:
- вызов ThreadLocal
get
метод. - Получить текущий поток t1.
- Получить переменную-член t1
ThreadLocalMap
. - Вычислить индекс массива Entry[] в ThreadLocalMap в соответствии с хэш-кодом ThreadLocal.
- Возвращает значение в позиции индекса. Таким образом, нам легко понять, почему только текущий поток может получить определенные значения, ведь эти значения хранятся непосредственно в переменной-члене ThreadLocalMap текущего потока, и роль ThreadLocal в этом процессе заключается в предоставлении Это уникальное значение хэш-кода, поэтому мы можем вычислить местоположение значения, которое мы сохранили в ThreadLocalMap.
Анализ исходного кода
Мы полностью анализируем его исходный код от создания ThreadLocal до вызова его методов set и get.
Конструктор
когда мы используемnew ThreadLocal<>()
Когда создается новый объект ThreadLocal, он инициализирует переменную-членthreadLocalHashCode
, эта переменная-член представляет значение хэш-кода текущего ThreadLocal и должно быть уникальным:
- Внутри ThreadLocal есть генератор статического hashCode.
nextHashCode
. - Каждый раз, когда создается новый объект ThreadLocal, вызывается метод синхронизации генератора для получения хэш-кода.
из-за зависимости от статических переменных-членов
nextHashCode
отношения, поэтому его хэш-код должен быть уникальным!
set(T t)
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- Получить текущий поток t.
- получить от т
ThreadLocalMap
карта. - Если карта не пуста, поместите текущее значение значения в карту.
- Если карта пуста, поставить новый поток ThreadLocalMap, т.е.ThreadLocalMap ThreadLocal — это внутренний класс со следующей структурой:
public class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private int size = 0;
private static final int INITIAL_CAPACITY = 16;
private int threshold; // Default to 0
}
Подобно конструкции внутри ArrayList, он имеетEntry
Таблица массива и запись наследуется от слабой ссылки (ThreadLocal, сохраненный во время gc, будет помечен для очистки), поэтому каждая запись содержит два значения,ThreadLocal
,value
, значение — это значение, которое мы хотим сохранить.
Далее мы возвращаемся назад и подробно анализируем третий шаг, метод set в ThreadLocalMap:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 1
for (Entry e = tab[i];
e != null; // 2
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 3
e.value = value;
return;
}
if (k == null) { // 4
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 5
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) // 6
rehash();
}
- Вычислите индекс i в записи в соответствии с хэш-кодом ThreadLocal.
- Выньте значение входа e, соответствующее i.
- Если ключ e равен текущему ThreadLocal, это означает, что в этой записи уже установлено такое же значение ThreadLocal, и значение этой записи заменяется напрямую.
- e ThreadLocal выше имеет значение null, представитель сборщика мусора готов восстановить Entry, пересчитать размер массива, перехэшировать.
- Позиция i не была инициализирована (ThreadLocal устанавливается в первый раз), и значение помещается непосредственно в позицию i.
- Разверните массив Entry.
get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
- Получить текущий поток.
- Получить ThreadLocalMap из текущего потока
- Найдите запись, соответствующую ThreadLocal из ThreadLocalMap.
- Если Entry не является нулевым, верните значение непосредственно в Entry.
- Вернуть исходное значение.
Среди них get(ThreadLocal tl) ThreadLocalMap выглядит следующим образом:Это то же самое, что и наш первоначальный анализ, вычисляет позицию индекса i в соответствии с переменной-членом хэш-кода ThreadLocal и получает Entry. Здесь также есть частные случаи, если ключ полученной Entry не равен текущему ThreadLocal, то это означает, что Entry будет обрабатываться сборщиком мусора.
getEntryAfterMiss
перефразировать, вычислить размер массива.
Меры предосторожности
Из приведенного выше анализа кода мы знаем, что жизненный цикл ThreadLocalMap синхронизирован с текущим потоком, и если текущий поток уничтожается, все ссылки в карте уничтожаются. Но что, если текущий поток не уничтожен (пул потоков, tomcat обрабатывает запросы и т. д.)? Слабая ссылка и значение ThreadLocal хранятся в Entry. ThreadLocal может быть очищен во время gc, и это значение не имеет места для доступа. В это время произойдет утечка памяти! Поэтому нам нужно вручную вызвать метод удаления, чтобы очистить ссылку текущего потока ThreadLocalMap!
Суммировать
- Значение, фактически сохраненное в ThreadLocal, все еще находится в ThreadLocalMap потока, а ThreadLocal просто использует значение своего хэш-кода в качестве промежуточной переменной вычисления.
- ThreadLocalMap использует массив Entry для хранения данных.Он получает значение индекса Entry в соответствии с хэш-кодом, рассчитанным ThreadLocal, а хэш-код ThreadLocal генерируется в его внутреннем статическом классе инструментов, поэтому конфликтов не будет.
- В режиме пула потоков жизненный цикл ThreadLocal всегда существует с потоком, и могут возникать утечки памяти, поэтому лучше вручную вызывать метод удаления.
关注我,这里只有干货!