Другие статьи можно найти в моем блоге – Код бесконечен.
Что такое ThreadLocal
ThreadLocal называется в "Java Core Technology Volume 1"локальная переменная потока(PS: подпишитесь на официальный аккаунт itweknow и ответьте на «Java Core Technology», чтобы получить книгу). Мы можем использовать ThreadLocal для создания переменных, которые могут быть прочитаны и записаны только одним и тем же потоком. Таким образом, даже если два потока выполняют один и тот же фрагмент кода, и этот код содержит ссылку на переменную ThreadLocal, два потока не могут видеть переменные ThreadLocal друг друга.
Простой в использовании
1. Чтобы создать ThreadLocal, вам нужно всего лишь создать новый объект ThreadLocal.
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
2. Установка значения
myThreadLocal.set("I'm a threadLocal");
3. Получите значение
myThreadLocal.get();
4. Очистить.В некоторых случаях после использования локальных переменных потока нам нужно их сразу очистить, иначе программа будет работать некорректно.
myThreadLocal.remove();
Если мы хотим использовать трудоемкий метод печати АОП сейчас, в это время нам нужно@BeforeМетод записывает время, когда метод начинает выполняться, а затем в@AfterReturningДля распечатки метода требуется время. Метод, который мы пишем в аспекте, может выполняться в нескольких потоках одновременно, поэтому на данный момент нам нужен ThreadLocal для записи времени начала выполнения.
1. Нам нужно определить ThreadLocal в классе аспектов.
private ThreadLocal<Long> threadLocal = new ThreadLocal();
2. В@BeforeВремя начала фиксируется в методе.
long startTime = System.currentTimeMillis();
threadLocal.set(startTime);
3. В@AfterReturningВремя начала выносится из метода и вычисляется времязатратность.
long startTime = threadLocal.get();
long spendTime = System.currentTimeMillis() - startTime;
threadLocal.remove();
System.out.println("方法执行时间:" + spendTime + "ms");
Вот только использовать эту сцену, чтобы ознакомиться с использованием ThreadLocal, Весь метод печати занимает много времени.Вы можете реализовать это на GithubНайдено на , если вы хотите узнать об АОП, вы можете обратиться к этой статье«Использование Spring Boot AOP для реализации обработки веб-журналов и распределенных блокировок».
Принципиальный анализ
По сути, ThreadLocal — это структура данных, давайте проанализируем принцип работы ThreadLocal через исходный код.
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
Выше это ThreadLocalget()иset()В исходном коде метода видно, что ThreadLocal сохраняет значение в ThreadLocalMap. Фактически в каждом потоке поддерживается переменная threadLocals (типа ThreadLocalMap).set()Метод фактически сохраняет значение в threadLocals текущего потока, вызываяget()Метод также берет значение из текущего потока, так что достигается изоляция между потоками.
Увидев это, вы должны быть удивлены, что нет ничего связанного с ключом при установке значения и получении значения.Так как же добиться взаимно-однозначного соответствия, когда поток имеет несколько ThreadLocals? Тогда давайте взглянем на этот класс ThreadLocalMap.
static class ThreadLocalMap {
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
}
Из приведенного выше видно, что он поддерживается в ThreadLocalMap.table,sizeа такжеthresholdтри свойства.tableмассив Entry, в основном используемый для хранения определенных данных,sizeдаtableразмер иthresholdЭто означает, что когдаtableКогда количество элементов превышает это значение,tableбудет расширяться. Разобравшись со структурой ThreadLocalMap, давайте взглянем на ееsetметод.
private void set(ThreadLocal<?> key, Object value) {
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();
}
Анализируя приведенный выше код, весь процесс настройки выглядит следующим образом:
- Найдите позицию i в таблице по значению threadLocalHashCode ThreadLocal.
- Если позиция i в таблице пуста, то создается новый объект Entry и помещается в позицию i.
- Если позиция i в таблице не пуста, вынуть ключ в позиции i.
- Если этот ключ является текущим объектом ThreadLocal, непосредственно измените значение объекта Entry в этом месте.
- Если ключ не является текущим объектом TreadLocal, найдите объект Entry в следующей позиции, а затем повторите описанные выше шаги для оценки.
Тот же принцип используется для получения значения из ThreadLocalMap для метода get. Так как же ThreadLocal генерирует значение threadLocalHashCode?
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
Видно, что когда мы инициализируем объект ThreadLocal, для него будет сгенерировано значение threadLocalHashCode, и значение будет увеличиваться на 0x61c88647 каждый раз при инициализации ThreadLocal. Таким образом, каждый ThreadLocal может найти место для хранения значения в ThreadLocalMap.
заключительные замечания
В конце статьи я поделился ямой, связанной с ThreadLocal, с которой столкнулся раньше: когда-то я использовал плагин PageHeler при написании пагинации, при цитировании пакета я неправильно указал PagerHelper под MybatisPlus, а PageHelper под MybatisPlus был в ThreadLocal Сохраненная информация о подкачке SQL не удаляется после использования, поэтому будут проблемы с SQL, выполняемым в текущем потоке, после выполнения SQL с разбивкой на страницы. Поэтому в процессе использования ThreadLocal необходимо обратить внимание на необходимость его очистки в подходящее время. В этой статье в основном рассказывается об использовании локальной переменной потока ThreadLocal в Java, и вы немного понимаете исходный код. Надеюсь, что смогу помочь всем.
PS: Обучение не останавливается, код не останавливается! Если вам нравятся мои статьи, подписывайтесь на меня!