Публичный аккаунт WeChat: маленький черный домовладелец
Поскольку дорога идет далеко, будущее будет лучше
Обучение безгранично, давайте работать вместе!
В предыдущей статье был представлен ThreadLocal, и я в целом знал сценарии применения и принципы реализации ThreadLocal.
Если кратко изложить одно предложение, то каждый Thread имеет атрибут threadLocals, представляющий собой ThreadLocalMap, в котором хранится массив Entry, ключ — слабая ссылка типа ThreadLocal, а значение — используемое значение. Все операции основаны на этой операции ThreadLocalMap.
Но у него есть ограничение: его нельзя передавать между родительским и дочерним потоками. то есть локальные переменные потока, установленные в родительском потоке, не могут быть доступны в дочернем потоке. Позже для решения этой проблемы был введен новый классInheritableThreadLocal.
После использования этого метода дочерний поток может получить доступ к локальной переменной потока родительского потока при создании дочернего потока.Принцип реализации состоит в том, чтобы скопировать локальную переменную потока, существующую в настоящее время в родительском потоке, в локальную переменную потока дочернего потока. когда родительский поток создает дочерний поток.
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
Как видно из приведенной выше структуры, она в основном переписанаgetMap,createMapметод.
В классе Thread есть две важные переменные.
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
# init()方法片段
Thread parent = currentThread();
.....
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
Когда дочерний поток передается в родительский поток путем вызоваnew Thread()метод для создания дочернего потока, метод Thread#init вызывается в конструкторе Thread.
В основном, чтобы сначала получить текущий объект потока, то есть родительский поток потока, который будет создан
Если родительский потокinheritableThreadLocalsне является нулевым, иinheritThreadLocalstrue (по умолчанию true), значение локальной переменной ingerit родительского потока используется для созданияinheritableThreadLocalsСтруктура, то есть копирование локальных переменных из родительского потока в дочерний.
private Entry[] table;
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
Методом по умолчанию дочернего потока для копирования родительского потока является мелкое копирование.Если вам нужно использовать глубокое копирование, если вам нужно использовать глубокое копирование, вам нужно использовать собственный ThreadLocal, унаследовать InheritThreadLocal и переопределить метод childValue.
Принцип его реализации в основном заключается в использовании значения локальной переменной в родительском потоке при создании дочернего потока.parent.inheritableThreadLocalsКопировать в дочерний поток, то есть возможность копирования есть при создании дочернего потока.
Но и у него есть недостаток, то есть в случае использования пула потоков, т.к. пул потоков является мультиплексным потоком, он не будет создаваться повторно, а указанное вышеinheritableThreadLocalsТолько когда создается дочерний поток, значение родительского потока копируется в дочерний поток, но он не будет повторно создаваться в пуле потоков, поэтому после многократного использования значение внешнего потока, когда задача отправленный в первый раз, все еще записывается, что привело к ошибкам данных.
Так как решить это явление?
Просто копируя контекст родительского потока, когда пользовательский поток отправляет задачу в пул потоков, можно реализовать прозрачную передачу локальных переменных в вызове пула потоков. Основываясь на этой идее, Али предложилTransmittableThreadLocalДобрый.
RunableилиCallableдолжны быть упакованы какTtlRunableилиTtlCallable.TransmittableThreadLocalПереопределите set, get и remove, которые реализуют ThreadLocal, и работа по фактическому сохранению значения inheritableThreadLocal по-прежнему выполняется родительским классом inheritableThreadLocal.TransmittableThreadLocalПросто ведите отдельную запись о том, какие объекты TransmittableThreadLocal хранятся для каждого потока, который их использует.
public final void set(T value) {
super.set(value);
if (null == value) removeValue();
else addValue();
}
Во-первых, он вызовет метод set класса InheritableThreadLocal родительского класса и добавит значение к объекту Thread.inheritableThreadLocalsв переменной.
Если значение равно null, вызывается метод removeValue(), в противном случае вызывается метод addValue.
private void addValue() {
if (!holder.get().containsKey(this)) { // @1
holder.get().put(this, null); // WeakHashMap supports null value.
}
}
private void removeValue() {
holder.get().remove(this);
}
перечислитьaddValueметод, сохранит текущий ThreadLocal вTransmittableThreadLocalГлобальная статическая переменная hodler. Все привязано к потокуTransmittableThreadLocalОбъекты хранятся в этом держателе, держатель предназначен только для записи того, к чему привязан текущий поток.TransmittableThreadLocalобъект.
Поговорим об этом подробноTtlRunableпринцип.
private TtlRunnable(@Nonnull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
Сначала создайте контейнер Map для хранения локальных переменных потока родительского потока.Ключевым является поток TransmittableLocal во время выполнения родительского потока, храните в нем значения в потоке. По умолчанию используется поверхностная копия.Если требуется глубокая копия, метод copyValue необходимо переписать.
public void run() {
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}
TtlRunable реализован в Runable, поэтому пул потоков выполняет TtlRunable, но метод запуска Runable выполняется в методе запуска TtlRunnable.
В конструкторе TtlRunable вызывается функция capture() для получения всех контекстов в текущем потоке и сохранения их в AtomicReference.
Когда поток выполняется, вызовите метод запуска TtlRunable, TtlRunable получит контекст в вызывающем потоке из AtomicReference и использует метод воспроизведения для копирования контекста в текущий поток и резервного копирования контекста.
Когда поток завершит выполнение, вызовите restore для передачи в контексте резервной копии, восстановите контекст резервной копии, удалите вновь добавленный контекст и повторно скопируйте контекст в текущий поток. Это выполнение не загрязнит исходный контекст потока в пуле потоков.