Как Java ThreadLocal реализован под капотом? - PullRequest
72 голосов
/ 29 июля 2009

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

Ответы [ 5 ]

102 голосов
/ 27 марта 2013

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

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

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

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

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 будет сделано, его карта будет автоматически очищена от мусора и все локальные объекты будут очищены. Даже если удерживать Thread, объекты ThreadLocal удерживаются по слабой ссылке и могут быть очищены, как только объект ThreadLocal выходит из области видимости.


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

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

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

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

32 голосов
/ 29 июля 2009

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

8 голосов
/ 29 июля 2009

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

2 голосов
/ 12 ноября 2015

Предположим, вы собираетесь реализовать 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 a 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();
}

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

The Whole Picture

0 голосов
/ 30 ноября 2017

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

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

Ссылка: JCIP

...