Другие статьи можно найти в моем блоге – Код бесконечен.
Что такое 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: Обучение не останавливается, код не останавливается! Если вам нравятся мои статьи, подписывайтесь на меня!