Как Java ThreadLocal реализован под капотом?

Как реализован ThreadLocal? Это реализовано в Java (с использованием некоторой параллельной карты от ThreadID к объекту), или это использует некоторую ловушку JVM, чтобы сделать это более эффективно?

5 ответов

Решение

Все ответы здесь верны, но немного разочаровывают, так как они несколько затуманивают, насколько умны ThreadLocalРеализация есть. Я просто смотрел на исходный кодThreadLocal и был приятно впечатлен тем, как это реализовано.

Наивная реализация

Если бы я попросил вас реализовать ThreadLocal<T> класс, учитывая API, описанный в javadoc, что бы вы сделали? Начальная реализация, вероятно, будет ConcurrentHashMap<Thread,T> с помощью Thread.currentThread() как его ключ. Это будет работать достаточно хорошо, но имеет некоторые недостатки.

  • Поток нитей - ConcurrentHashMap Это довольно умный класс, но в конечном итоге он все же должен иметь дело с тем, чтобы не допустить какого-либо перехвата нескольких потоков с ним, и если различные потоки будут регулярно попадать в него, будут замедления.
  • Постоянно хранит указатель как на Thread, так и на объект, даже после того, как Thread завершен и может быть GC'ed.

GC-дружественная реализация

Хорошо, попробуйте еще раз, давайте справимся с проблемой сбора мусора, используя слабые ссылки. Работа с WeakReferences может сбивать с толку, но этого должно быть достаточно, чтобы использовать карту, построенную так:

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())

Или, если мы используем гуаву (и мы должны быть!):

new MapMaker().weakKeys().makeMap()

Это означает, что когда никто больше не удерживает поток (подразумевая, что он закончен), ключ / значение могут быть собраны сборщиком мусора, что является улучшением, но все еще не решает проблему конфликта потоков, то есть до сих пор наш ThreadLocal не все так удивительно в классе. Кроме того, если кто-то решил держаться Thread объекты после того, как они закончили, они никогда не будут GC ', и, следовательно, не будут наши объекты, даже если они технически недоступны сейчас.

Умная реализация

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

Реализация будет выглядеть примерно так:

// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()

Здесь нет необходимости беспокоиться о параллелизме, потому что только один поток будет когда-либо получать доступ к этой карте.

Здесь разработчики Java имеют большое преимущество перед нами - они могут напрямую разрабатывать класс Thread и добавлять к нему поля и операции, и это именно то, что они сделали.

В java.lang.Threadесть следующие строки:

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

Что, как следует из комментария, действительно является частно-пакетным отображением всех значений, отслеживаемыхThreadLocalобъекты для этогоThread, Реализация ThreadLocalMap это не WeakHashMap, но он следует тому же основному договору, включая хранение его ключей по слабой ссылке.

ThreadLocal.get()затем реализуется так:

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();
}

А такжеThreadLocal.setInitialValue()вот так:

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;
}

По сути, используйте картув этой теме, чтобы держать все наши ThreadLocalобъекты. Таким образом, нам никогда не нужно беспокоиться о значениях в других потоках (ThreadLocal буквально может получить доступ только к значениям в текущем потоке) и поэтому не имеет проблем с параллелизмом. Кроме того, как только Thread сделано, его карта будет автоматически GC'а и все локальные объекты будут очищены. Даже еслиThread удерживается на ThreadLocal объекты удерживаются по слабой ссылке, и могут быть очищены, как только ThreadLocal объект выходит из области видимости.


Излишне говорить, что эта реализация произвела на меня большое впечатление. Она довольно элегантно решает многие проблемы параллелизма (по общему признанию, используя преимущества ядра Java, но это простительно, поскольку это такой умный класс) и позволяет быстро и потокобезопасный доступ к объектам, доступ к которым необходим только одному потоку за раз.

ТЛ; дрThreadLocalРеализация довольно крутая и намного быстрее / умнее, чем вы думаете на первый взгляд.

Если вам понравился этот ответ, вы также можете оценить мое (менее подробное) обсуждениеThreadLocalRandom,

Thread/ThreadLocalфрагменты кода, взятые из реализации Java 8 Oracle/OpenJDK.

Ты имеешь в виду java.lang.ThreadLocal, Это довольно просто, на самом деле, это просто карта пар имя-значение, хранящихся внутри каждого Thread объект (см. Thread.threadLocals поле). API скрывает эту деталь реализации, но это более или менее все, что нужно сделать.

Переменные ThreadLocal в Java работают посредством доступа к HashMap, который хранится в экземпляре Thread.currentThread().

Предположим, вы собираетесь реализовать ThreadLocalКак вы делаете это для конкретных потоков? Конечно, самый простой метод - создать нестатическое поле в классе Thread, назовем его threadLocals, Поскольку каждый поток представлен экземпляром потока, поэтому threadLocals в каждой теме тоже было бы по разному. И это также то, что делает Java:

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

Что такое ThreadLocal.ThreadLocalMap Вот? Потому что у вас есть только threadLocals для потока, так что если вы просто берете threadLocals как твой ThreadLocal(скажем, определите threadLocals как Integer), у вас будет только один ThreadLocal для конкретной темы. Что делать, если вы хотите несколько ThreadLocal переменные для потока? Самый простой способ сделать threadLocals HashMap, key каждой записи является имя ThreadLocal переменная, а value каждой записи является значением ThreadLocal переменная. Немного смущает? Допустим, у нас есть две темы, t1 а также t2, они принимают то же самое Runnable экземпляр как параметр Thread конструктор, и у них обоих есть два ThreadLocal названные переменные tlA а также tlb, Вот на что это похоже.

t1.tlA

+-----+-------+
| Key | Value |
+-----+-------+
| tlA |     0 |
| tlB |     1 |
+-----+-------+

t2.tlB

+-----+-------+
| Key | Value |
+-----+-------+
| tlA |     2 |
| tlB |     3 |
+-----+-------+

Обратите внимание, что значения составлены мной.

Теперь это кажется идеальным. Но что это ThreadLocal.ThreadLocalMap? Почему он просто не использовал HashMap? Чтобы решить эту проблему, давайте посмотрим, что происходит, когда мы устанавливаем значение через set(T value) метод ThreadLocal учебный класс:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

getMap(t) просто возвращается t.threadLocals, Так как t.threadLocals был инициирован в nullИтак, мы входим createMap(t, value) первый:

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

Это создает новый ThreadLocalMap экземпляр с использованием текущего ThreadLocal экземпляр и значение, которое будет установлено. Посмотрим что ThreadLocalMap это как на самом деле часть ThreadLocal учебный класс

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);
            value = v;
        }
    }

    ...

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    ...

}

Основная часть ThreadLocalMap класс Entry class, который расширяет WeakReference, Это гарантирует, что если текущий поток завершится, он будет автоматически очищен от мусора. Вот почему он использует ThreadLocalMap вместо простого HashMap, Проходит ток ThreadLocal и его значение в качестве параметра Entry класс, поэтому, когда мы хотим получить значение, мы можем получить его от table, который является примером Entry учебный класс:

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();
}

Вот как это выглядит на всей картине:

Вся картина

Концептуально, вы можете думать о ThreadLocal<T> как проведение Map<Thread,T> который хранит специфичные для потока значения, хотя это не так, как это на самом деле реализовано.

Специфичные для потока значения хранятся в самом объекте Thread; когда поток завершается, его значения могут быть собраны сборщиком мусора.

Ссылка: JCIP

Другие вопросы по тегам