Ответ 1
Все ответы здесь правильные, но немного разочаровывающие, поскольку они несколько замалчивают, как умная реализация ThreadLocal
. Я просто смотрел исходный код для ThreadLocal
и был приятно впечатлен тем, как он реализован.
Наивная реализация
Если бы я попросил вас реализовать класс ThreadLocal<T>
с учетом API, описанного в javadoc, что бы вы сделали? Первоначальная реализация, скорее всего, будет ConcurrentHashMap<Thread,T>
с использованием Thread.currentThread()
в качестве ее ключа. Это будет работать достаточно хорошо, но имеет некоторые недостатки.
- Конкуренция по теме -
ConcurrentHashMap
- довольно умный класс, но в конечном итоге он все равно должен иметь дело с предотвращением чередования нитей от него любым способом, и если разные потоки ударяют его регулярно, произойдет замедление. - Постоянно сохраняет указатель как на Thread, так и на объект, даже после завершения Thread и может быть GC'ed.
Реализация, совместимая с GC
Повторите попытку, давайте рассмотрим проблему сбора мусора, используя слабые ссылки. Работа с WeakReferences может быть запутанной, но ее должно быть достаточно, чтобы использовать построенную таким образом карту:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
Или, если мы используем Guava (и мы должны быть!):
new MapMaker().weakKeys().makeMap()
Это означает, что после того, как никто не удерживает Thread (подразумевая, что он закончен), ключ/значение может быть собрано в мусор, что является улучшением, но все же не затрагивает проблему конфликта нитей, что означает, что наш ThreadLocal
не все, что удивительно класс. Кроме того, если кто-то решил удержать объекты Thread
после того, как они закончили, они никогда не будут GC'ed, и поэтому и наши объекты, даже если они технически недоступны, теперь не будут.
Умная реализация
Мы думали о ThreadLocal
как сопоставление потоков с ценностями, но, возможно, это не самый правильный способ подумать об этом. Вместо того, чтобы думать о нем как о сопоставлении потоков с значениями в каждом объекте ThreadLocal, что, если мы думали об этом как о сопоставлении объектов ThreadLocal с значениями в каждом потоке? Если каждый поток хранит отображение, а ThreadLocal просто обеспечивает хороший интерфейс в этом сопоставлении, мы можем избежать всех проблем предыдущих реализаций.
Реализация будет выглядеть примерно так:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
Здесь нет необходимости беспокоиться о concurrency, потому что только один поток будет когда-либо получать доступ к этой карте.
У разработчиков 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
. Таким образом, нам не нужно беспокоиться о значениях в других Threads (ThreadLocal
буквально может обращаться только к значениям в текущем потоке) и поэтому не имеет проблем concurrency. Кроме того, как только Thread
будет завершено, его карта автоматически будет GC'ed, и все локальные объекты будут очищены. Даже если Thread
удерживается, объекты ThreadLocal
сохраняются слабым эталоном и могут быть очищены, как только объект ThreadLocal
выходит за пределы области видимости.
Излишне говорить, что я был впечатлен этой реализацией, он довольно элегантно обходит множество проблем concurrency (по общему признанию, пользуясь тем, что являюсь частью основной Java, но это прощает их, так как это такой умный класс) и обеспечивает быстрый и потокобезопасный доступ к объектам, к которым только один раз нужно обращаться одним потоком.
tl; dr ThreadLocal
реализация довольно крутая и намного быстрее/умнее, чем вы могли бы подумать на первый взгляд.
Если вам понравился этот ответ, вы также можете оценить мой (менее подробный) обсуждение ThreadLocalRandom
.
Thread
/ThreadLocal
фрагменты кода, взятые из реализация Oracle/OpenJDK Java 8.