Детали ThreadLocal

Java

Другие статьи можно найти в моем блоге – Код бесконечен.

Что такое 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();
}

Анализируя приведенный выше код, весь процесс настройки выглядит следующим образом:

  1. Найдите позицию i в таблице по значению threadLocalHashCode ThreadLocal.
  2. Если позиция i в таблице пуста, то создается новый объект Entry и помещается в позицию i.
  3. Если позиция i в таблице не пуста, вынуть ключ в позиции i.
  4. Если этот ключ является текущим объектом ThreadLocal, непосредственно измените значение объекта Entry в этом месте.
  5. Если ключ не является текущим объектом 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: Обучение не останавливается, код не останавливается! Если вам нравятся мои статьи, подписывайтесь на меня!

扫码关注“代码无止境”