ThreadLocal ThreadLocalMap? круг

Java

Сначала лайкните, а потом смотрите, формируйте привычку 🌹 Добро пожаловать в WeChat, чтобы подписаться[Java编程之道], Делайте небольшие успехи каждый день и делитесь знаниями о технологии осаждения.

Чат ThreadLocal

Фронт в моем репозитории GitHubV-LoggingToolНекоторые из них просто использовали ThreadLocal, в основном в классе аспектов.Функционально необходимо временно хранить информацию о пользователе, перехваченную предварительным расширением.При выполнении пострасширения информация о пользователе извлекается из ThreadLocal и используется .

Сегодня давайте поговорим о соответствующих знаниях о ThreadLocal и узнаем о его структуре данных, использовании, принципах и т. д. Давайте углубимся...

Я прочитал много объяснений о ThreadLocal в Интернете.Исходный код относительно прост, но связь между Thread, ThreadLocal и ThreadLocalMap немного неясна, особенно та, которая никогда не меняется.Схема внутренней структуры ThreadLocalэм... Мне действительно потребовалось много времени, чтобы понять, что происходит.

Вспомогательный класс ThreadLocal

ThreadLocal — это класс инструментов копирования переменных локального потока, который в основном используется для сопоставления частного потока и объекта копирования, хранящегося в потоке, и переменные между каждым потоком не мешают друг другу.

То, что сказал чиновник, все еще относительно ясно, уточните ключевые слова工具类, на мой взгляд, ThreadLocal — это класс инструмента, предоставляемый каждому потоку для управления переменными, что позволяет достичь цели изоляции переменных между потоками.

е

Далее стоит посмотреть на картинку и поговорить:

  • Внутри каждого потока Thread есть ThreadLocalMap.
  • Карта хранит локальный объект потока ThreadLocal (ключ) и копию переменной (значение) потока.
  • Карта внутри Thread поддерживается ThreadLocal, а ThreadLocal отвечает за получение и установку значения переменной потока на карту.
  • Поток может иметь несколько ThreadLocals.

Каждый поток имеет свою собственную уникальную структуру карты, и карта содержит пару ключ-значение, где ThreadLocal является копией переменной Key в качестве хранилища, чтобы достичь цели изоляции переменных.

Как вы обычно используете ThreadLocal?

package threadlocal;

/**
 * @Auther: Xianglei
 * @Company: Java编程之道
 * @Date: 2020/7/2 21:44
 * @Version 1.0
 */
public class main {
    private static ThreadLocal<String> sThreadLocal = new ThreadLocal<>();
    public static void main(String args[]) {
        sThreadLocal.set("这是在主线程中");
        System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
        //线程a
        new Thread(new Runnable() {
            @Override
            public void run() {
                sThreadLocal.set("这是在线程a中");
                System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
            }
        }, "线程a").start();
        //线程b
        new Thread(new Runnable() {
            @Override
            public void run() {
                sThreadLocal.set("这是在线程b中");
                System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
            }
        }, "线程b").start();
        //线程c  
        new Thread(() -> {
            sThreadLocal.set("这是在线程c中");
            System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
        }, "线程c").start();
    }
}

Вывод выглядит следующим образом

线程名字:main---这是在主线程中
线程名字:线程b---这是在线程b中
线程名字:线程a---这是在线程a中
线程名字:线程c---这是在线程c中
Process finished with exit code 0

Видно, что каждый поток обращается к данным в своем собственном ThreadLocalMap через ThreadLocal, и явление грязного чтения отсутствует. Это связано с тем, что каждый поток сохранил пару ключ-значение, чей ThreadLocal является копией переменной Key в качестве Vault. (изолированный)

Возможно, вы немного запутались, как ThreadLocal копирует переменные в ThreadLocalMap?

Давай поболтаем...

Когда мы инициализируем поток, он внутри создаетThreadLocalMap的Map容器поддерживать.

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

Когда ThreadLocalMap создается и загружается, его статический внутренний класс Entry также загружается для завершения действия инициализации.

 static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
       Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
}

На этом этапе контейнер Map внутри потока Thread был инициализирован, поэтому как он связан с ThreadLocal и как ThreadLocal управляет отношениями между парами ключ-значение.

Анализ ThreadLocal

Давайте проанализируем внутреннюю логику его основного метода и ответим на поставленные выше вопросы:

  • Метод set() используется для сохранения значения переменной копирования текущего потока.

  • Метод get() используется для получения значения переменной копирования текущего потока.

  • initialValue() — это начальное значение переменной копии текущего потока.

  • Метод remove() удаляет значение переменной копирования текущего потока.

установить метод

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Объясните, и вы поймете:

Когда мы вызываем метод set внутри Thread:

  • Первый шаг – получить调用当前方法的线程Thread.
  • Затем плывите по течению, чтобы получить текущий线程内部изThreadLocalMapконтейнер.
  • Наконец, поместите переменную副本Бросьте его.

Нет... Вы понимаете, ThreadLocal (думайте о нем как об инструменте для поддержки переменных внутри потоков!) просто работает внутри потоков во время SetThreadLocalMapПеременная копируется в контейнер Map внутри Thread, Key — это текущий ThreadLocal, а Value — это копия переменной.

получить метод

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}
  • Получить объект ThreadLocalMap текущего потока
  • Получите узел Entry, сохраненный потоком в соответствии с этим (текущий объект threadlocal) с карты.
  • Получите сохраненное соответствующее значение копии Value из узла Entry и верните его.
  • Если карта пуста, возвращается начальное значение null, то есть копия переменной потока равна null.

метод удаления

Очистить KV на карте

/**
 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain #set set} by the current thread
 * in the interim.  This may result in multiple invocations of the
 * <tt>initialValue</tt> method in the current thread.
 *
 * @since 1.5
 */
public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

 /**
  * Remove the entry for key.
  */
    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) {
                  e.clear();
                  expungeStaleEntry(i);
                  return;
              }
          }
    }

Давай встретимся сноваThreadLocalMap, вещь, которая фактически хранит (изолирует) данные.

ThreadLocalMap

ThreadLocalMap — это ThreadLocal内部类, реализовал набор собственной структуры карты, мы можем сразу увидеть внутренние отношения наследования.

Его Entry использует метод K-V для организации данных.Ключом в Entry является объект 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;
        }
 }

Переменные-члены 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
}

Расчет хэш-кода

ThreaLocalMap не использует традиционный метод хэш-кода для вызова ThreadLocal (хэш-код, унаследованный от объекта), а вызываетnexthashcode, исходный код выглядит следующим образом:

private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
 //1640531527 能够让hash槽位分布相当均匀
private static final int HASH_INCREMENT = 0x61c88647; 
private static int nextHashCode() {
      return nextHashCode.getAndAdd(HASH_INCREMENT);
}

Хэш-конфликт

Самое большое отличие от HashMap заключается в том, что ThreadLocalMap решает конфликты хэшей, просто добавляя или вычитая 1 из размера шага и линейного обнаружения, чтобы найти следующее соседнее местоположение.

/**
 * Increment i modulo len.
 */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

/**
 * Decrement i modulo len.
 */
private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}

ThreadLocalMap использует линейное обнаружение для разрешения конфликтов хэшей, что неэффективно: если в карту помещается большое количество разных объектов ThreadLocal, отправляются конфликты. Поэтому рекомендуется хранить только одну переменную (один ThreadLocal) в каждом потоке, чтобы не возникало проблем с конфликтом хэшей. Если поток хочет сохранить несколько переменных, ему необходимо создать несколько ThreadLocals. Когда несколько ThreadLocal помещаются в Карта, она будет очень большой.Увеличится вероятность хеш-конфликта.

Это понятно? Когда вам нужно сохранить несколько переменных в потоке, вы думаете, что это несколько наборов? Вы ошибаетесь, вам нужно создать несколько ThreadLocals, а несколько наборов не могут достичь цели хранения нескольких переменных.

sThreadLocal.set("这是在线程a中");

Проблема слабой ссылки ключа

Посмотрите на мандарин, зачем использовать слабые ссылки.

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
чтобы обработать非常大и生命周期Очень длинный поток, хеш-таблица использует слабые ссылки в качестве ключей.

  • Долгий жизненный цикл: временно подумайте о потоках в пуле потоков

ThreadLocal похож на Thread, когда нет строгой ссылки на внешние объекты.При сборке мусора слабая ссылка Key будет переработана, а Value будет сильной ссылкой.Значение значения может не перерабатываться все время, и возникает утечка памяти произойдет.

  • Если для ключа используется сильная ссылка: ссылочный объект ThreadLocal перезапускается, но ThreadLocalMap по-прежнему содержит строгую ссылку на ThreadLocal. Если он не удален вручную, ThreadLocal не будет переработан, что приведет к утечке памяти Entry.

  • В ключе используется слабая ссылка: указанный объект ThreadLocal повторно используется. Поскольку ThreadLocalMap содержит слабую ссылку на ThreadLocal, даже если он не удален вручную, ThreadLocal будет повторно использован. значение будет очищено при следующем вызове ThreadLocalMap set, get, remove.

Некоторые оптимизации были сделаны в Java 8. Например, при вызове методов get(), set() и remove() класса ThreadLocal значение с нулевым ключом во всех элементах Entry в потоке ThreadLocalMap будет очищено, а вся запись будет установлена ​​​​на ноль, что способствует следующему восстановлению памяти.

В Java8 цикл for проходит через весь массив Entry, и если встречается key=null, он будет заменен, чтобы избежать проблемы утечки памяти.

       private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

Обычно жизненный цикл ThreadLocalMap равен Thread (обратите внимание на Thread в пуле потоков), если соответствующий ключ не удалить вручную (поток возвращается в пул потоков после использования потока, KV в нем равен больше не используется, но не будет переработан GC. Можно считать, что это утечка памяти), что обязательно приведет к утечкам памяти, но использование слабых ссылок может обеспечить дополнительный уровень защиты: слабая ссылка ThreadLocal будет будет восстановлен сборщиком мусора, и утечки памяти не будет, а соответствующее значение будет сохранено в следующем ThreadLocalMap调用set,get,remove的时候会被清除, Java8 выполнил вышеуказанную оптимизацию кода.

Суммировать

  • Каждый ThreadLocal может сохранить только одну копию переменной.Если вы хотите, чтобы поток мог сохранять более одной копии, вам необходимо создать несколько ThreadLocal.
  • Ключ ThreadLocalMap внутри ThreadLocal является слабой ссылкой, и существует риск утечки памяти.
  • Каждый раз, когда используется ThreadLocal, вызывается его метод remove() для очистки данных.