Исходный код | Принцип реализации ThreadLocal

интервью Java

ThreadLocal также называют «локальной переменной потока», «локальной переменной потока»:

  • Его объем охватывает поток, а не конкретную задачу;
  • Его «естественное» время жизни «такое же», как и время жизни потока (но короче, чем время жизни потока в реализации JDK, что снижает вероятность утечек памяти).

ThreadLocal представляет собойРазделение потоков и задачидеи, чтобы достичь线程封闭Цель состоит в том, чтобы помочь нам разработать более «здоровые» (простые, красивые и легкие в обслуживании) потокобезопасные классы.

иллюзия

Использование ThreadLocal часто создает иллюзию того, что переменные инкапсулированы внутри объекта задачи.

По способу использования спекулировать на идее реализации

Когда мы используем объекты ThreadLocal, мы часто объявляем переменные ThreadLocal внутри объекта задачи, например:

ThreedLocal<List> onlineUserList = …;

Вот почему здесь сказано, что «концептуально» можно рассматривать как таковое. Тогда с точки зрения этого метода использования (обратное мышление) давайте, естественно, подумаем, чтоПеременные ThreadLocal хранятся внутри объекта задачи., то идея реализации такова:

class ThreadLocal<T>{
    private Map<Thread, T> valueMap = …;
    public T get(Object key){
        T realValue = valueMap.get(Thread.currentThread())
        return realValue;
    }
}

Существует проблема

Но есть проблема с этой реализацией:

  • После того, как поток умирает, объект задачи может все еще существовать (это наиболее распространенный случай), поэтому объект ThreadLocal все еще существует. Мы не можем попросить поток активно удалить объект ThreadLocal, который он использует, прежде чем он умрет, поэтому запись(), соответствующая потоку в valueMap, не может быть перезапущена;

Суть проблемы в том, что эта реализация«Включить поля, связанные с потоками, в объекты задач, а не в потоки». Итак, самый важный момент в реализации ThreadLocal —"включить поля, связанные с потоком, в текущий экземпляр потока", хотя поля по-прежнему объявляются, устанавливаются и получаются в объекте задачи независимо от объекта задачи.

Анализ исходного кода

Реализация ThreadLocal анализируется из приведенного ниже исходного кода.

Откат исходного кода

Сначала просмотрите исходный код класса ThreadLocal и найдите конструктор:

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

пустой.

Посмотрите непосредственно на метод set:

    /**
     * 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); // 使用this引用作为key,既做到了变量相关,又满足key不可变的要求。
        else
            createMap(t, value);
    }

При установке значения должна быть получена карта типа ThreadLocalMap по текущему потоку t, и в этой карте хранится реальное значение. Это частично подтверждает предыдущую идею — переменные ThreadLocal хранятся в“线程相关”的mapсередина.

Введите метод getMap:

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

Как видите, карта на самом деле сохраняется в экземпляре Thread, который является текущим потоком, переданным ранее.

Глядя на исходный код класса Thread, действительно есть объявление переменной threadLocals:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

В этой реализации переменная ThreadLocal уже соответствует основным требованиям, предложенным в начале статьи:

  • Его объем охватывает поток, а не конкретную задачу
  • Его «естественное» время жизни «такое же», как и время жизни потока.

Если это интервью, то на этом общий анализ можно закончить.

Передовой

Если вы хотите пойти дальше, вы можете продолжить просмотр исходного кода класса ThreadLocal.ThreadLocalMap:

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k); // 使用了WeakReference中的key
                value = v;
            }
        }

        /**
         * 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;

Видно, что хотя ThreadLocalMap не реализует интерфейс Map, он также обладает большинством свойств общих классов реализации Map (в отличие от HashMap, когда хэш повторяется, он последовательно проходится в таблице).

Что имеет значение, так это реализация Entry. Entry наследует общую ссылку WeakReference ThreadLocal.

  1. ThreadLocal описывает тип Entry, который сохраняет переменную ThreadLocal.
  2. WeakReference предназначен для облегчения сборки мусора.

WeakReference и утечка памяти

утечка памяти, которая все еще существует

Теперь мы в порядке"включить поля, связанные с потоком, в текущий экземпляр потока", если поток умирает, экземпляр ThreadLocalMap в потоке также будет перезапущен.

Вроде бы все и так хорошо, но мы упустили из виду очень серьезную проблему -Если объект задачи завершается, а экземпляр потока все еще существует (обычно при использовании пулов потоков, и экземпляры потоков необходимо использовать повторно), то утечки памяти все равно будут происходить.. Можно предположить, что большинство яверов вручную удаляют объявленные переменные ThreadLocal, но такой синтаксис возврата в C++ не приемлем для яверовцев; кроме того,Попросить меня запомнить объявленные переменные сложнее, чем попросить девушку поесть.

Используйте WeakReference для уменьшения утечек памяти

Анализ исходного кода ThreadLocal впервые позволил мне понять, как использовать WeakReference.

Для слабой ссылки WeakReference, когда на объект указывает только слабая ссылка, но не указывает никакая другая сильная ссылка StrongReference, если сборщик мусора работает, объект будет собран.

В процессе использования переменной ThreadLocal, так как только объект задачи имеет сильную ссылку на переменную ThreadLocal (рассмотрим простейший случай), поэтомуПосле повторного использования объекта задачи нет строгой ссылки на переменную ThreadLocal., переменная ThreadLocal также будет переработана.

Причина, по которой мы говорим «уменьшить утечку памяти», заключается в том, что простое использование WeakReference решает только первую половину проблемы.

Дальнейшее сокращение утечек памяти

Несмотря на то, что теперь используются слабые ссылки, в ThreadLocalMap по-прежнему происходят утечки памяти. Причина очень проста, переменная ThreadLocal — это всего лишь ключ в Entry, поэтомуХотя ключ в записи переработан, на саму запись по-прежнему ссылаются..

Чтобы решить эту вторую половину проблемы,ThreadLocalMap будет активно очищать запись, чей ключ имеет значение null в ThreadLocalMap в своих методах getEntry, set, remove, rehash и других..

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

В то же время следует отметить, что здесь происходит «естественный» жизненный цикл объекта ThreadLocal."подтянуть"некоторые, и, таким образом, короче, чем жизненный цикл потока.

Другие утечки памяти

Существует также более общая ситуация с утечкой памяти:Объявите переменные с помощью статического ключевого слова.

Использование статического ключевого слова продлевает жизненный цикл переменной и может привести к утечке памяти. То же самое верно и для переменных ThreadLocal.

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


Ссылаться на:


Ссылка на эту статью:Исходный код | Принцип реализации ThreadLocal
автор:обезьяна 007
Источник:monkeysayhi.github.io
Эта статья основана наCreative Commons Attribution-ShareAlike 4.0Выпущено по международному лицензионному соглашению, приветствуется перепечатка, вывод или использование в коммерческих целях, но авторство и ссылка на эту статью должны быть сохранены.