Как 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