1. Información general
ThreadLocal (TL) es un mecanismo de implementación de variables locales de subprocesos en Java. Proporciona una copia de variable separada para cada subproceso para garantizar la seguridad de subprocesos de las variables en escenarios de subprocesos múltiples. A menudo se usa en lugar del paso explícito de parámetros.
InheritableThreadLocal (ITL) es una versión mejorada de TL proporcionada por JDK, y TransmittableThreadLocal (TTL) es una versión mejorada de ITL de código abierto de Alibaba
Estos ThreadLocals tienen diferentes usos en diferentes escenarios, analicémoslos:
2, hilo local
Hay cuatro métodos principales de ThreadLocal: initialValue, set, get, remove
2.1. Inicialización——valorInicial
Cuando un subproceso accede a ThreadLocal por primera vez (ThreadLocal.get()), se inicializará y asignará. Comúnmente usamos dos métodos para inicializar ThreadLocal
2.1.1, reescribir valorInicial
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "";
}
};
2.1.2 Llamar a ThreadLocal.withInitial
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");
Creará una clase interna SuppliedThreadLocal
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
Esta clase anula el método initialValue
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
//当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化
return supplier.get();
}
}
⚠️ITL no vuelve a implementar withInitial. Si usa withInitial, creará STL y perderá sus características mejoradas
2.2 Asignación——conjunto
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
Aquí aparece un atributo clave ThreadLocalMap , la clase se define en ThreadLocal, que es una variable miembro de Thread
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
También hay una entrada de clase interna dentro de ThreadLocalMap, que es donde se almacena el valor
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
//ThreadLocal的引用是“key”
super(k);
//线程局部变量是value
value = v;
}
}
//Entry数组
//value具体放在哪个index下,是由ThreadLocal的hashCode算出来的
private Entry[] table;
}
2.3, valor - obtener
public T get() {
Thread t = Thread.currentThread();
//1、获取线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//2、根据ThreadLocal的hashCode,获取对应Entry下的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//3、如果没有赋过值,则初始化
return setInitialValue();
}
2.4. Vaciar——eliminar
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//会将对应Entry、包括他的key、value手动置null
m.remove(this);
}
3, hilo heredado local
3.1 Problemas de TL en el escenario de subprocesos padre-hijo
Primero veamos un ejemplo
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "A");
threadLocal.set("B");
Thread thread = new Thread(() -> {
System.out.println("子线程ThreadLocal:" + threadLocal.get());
}, "子线程");
thread.start();
thread.join();
}
Los resultados de la impresión son los siguientes: se puede ver que ThreadLocal del subproceso secundario es el valor inicial y el valor modificado del subproceso principal no se usa:
子线程ThreadLocal:A
El ThreadLocalMap del subproceso se crea cuando se accede a él por primera vez, por lo que cuando el subproceso secundario usa ThreadLocal, inicializará un nuevo ThreadLocal y la variable local del subproceso es el valor predeterminado.
⚠️Entonces, TL no es hereditario
3.2 La solución de ITL
Para resolver el problema de la herencia de subprocesos TL, JDK introdujo ITL
Heredó ThreadLocal y reescribió tres métodos de childValue, getMap y createMap
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);
}
}
Aquí aparece InheritableThreadLocals, que almacena el ThreadLocal copiado del subproceso principal. Este valor se asigna cuando el subproceso principal modifica ThreadLocal por primera vez y luego se copia cuando se crea el subproceso secundario.
//父线程部分:
public void set(T value) {
Thread t = Thread.currentThread();
//该方法被ITL重写,访问inheritableThreadLocals为null
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//该方法同样被ITL重写,创建一个ThreadLocalMap赋值给inheritableThreadLocals
createMap(t, value);
}
//子线程部分:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//省略一些代码...
//获取当前线程(父线程、也就是创建子线程的线程)
Thread parent = currentThread();
//1、允许ThreadLocal遗传(这个默认为true)
//2、inheritableThreadLocals不为空,因为父线程调用set了
//父线程不调用set,那ThreadLocal就是初始值,那直接初始化就好了,也不用进该分支
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
//createInheritedMap使用该构造函数,根据父线程的inheritableThreadLocals进行深拷贝
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//深拷贝父线程ThreadLocalMap
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) {
//childValue被ITL重写,返回父线程ThreadLocal的值
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++;
}
}
}
}
El efecto de usar ITL
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "A";
}
};
threadLocal.set("B");
Thread thread = new Thread(() -> {
System.out.println("子线程ThreadLocal:" + threadLocal.get());
}, "子线程");
thread.start();
thread.join();
}
Los resultados de la impresión son los siguientes, el subproceso secundario copió el subproceso principal ThreadLocal:
子线程ThreadLocal:B
En resumen, la idea central de ITL para resolver la herencia de subprocesos padre-hijo es colocar el ThreadLocal heredable en el nuevo ThreadLocalMap del subproceso principal y copiarlo cuando el subproceso secundario se usa por primera vez.
4. 、 TransmittableThreadLocal
4.1 Problemas de ITL en escenarios de reutilización de hilos
Comencemos con un ejemplo simple.
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "A";
}
};
threadLocal.set("B");
ExecutorService executorService = Executors.newFixedThreadPool(1);
//1、子线程第一次获取ThreadLocal
executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();
Thread.sleep(1000);
//2、父线程修改ThreadLocal
threadLocal.set("C");
System.out.println("父线程修改ThreadLocal为"+threadLocal.get());
//3、子线程第二次获取ThreadLocal
executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();
}
Los resultados de la impresión son los siguientes: cuando el subproceso secundario se imprime por segunda vez, no copia el ThreadLocal del subproceso principal, sino que utiliza el valor copiado por primera vez:
子线程ThreadLocal:B
父线程修改ThreadLocal为C
子线程ThreadLocal:B
⚠️Los subprocesos secundarios reutilizables no percibirán cambios en el subproceso principal ThreadLocal
4.2, solución TTL
4.2.1, el uso de TTL
TTL ha hecho una encapsulación un poco más compleja en ITL, comenzamos a entender por el uso
Introducir dependencias
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>latest</version>
</dependency>
Cuando se usa TTL, el subproceso debe ser encapsulado por TTL, y el grupo de subprocesos es el mismo
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadLocal<String> threadLocal = new TransmittableThreadLocal<String>() {
@Override
protected String initialValue() {
return "A";
}
};
threadLocal.set("B");
ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
Thread.sleep(1000);
threadLocal.set("C");
System.out.println("父线程修改ThreadLocal为" + threadLocal.get());
executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
Thread.sleep(1000);
executorService.submit(() -> {
threadLocal.set("D");
System.out.println("子线程修改ThreadLocal为" + threadLocal.get());
});
Thread.sleep(1000);
executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get()));
Thread.sleep(1000);
}
Los resultados de impresión son los siguientes, el subproceso secundario obtendrá el ThreadLocal del subproceso principal cada vez
子线程ThreadLocal:B
父线程修改ThreadLocal为C
子线程ThreadLocal:C
子线程修改ThreadLocal为D
子线程ThreadLocal:C
Desde el punto de vista del uso, TTL requiere encapsulación de tareas, por lo que comenzamos con ThreadLocal y ExecutorService.
4.2.2, encapsulación TTL de ThreadLocal
El siguiente es el valor y la lógica de asignación de TTL, que involucran un método clave addThisToHolder, y el titular del atributo correspondiente se usará cuando el grupo de subprocesos ejecute tareas
//TransmittableThreadLocal.addThisToHolder()
private void addThisToHolder() {
//InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder
if (!holder.get().containsKey(this)) {
//holder是静态变量,他会把TTL存到当前线程的map中
//value是null,他其实是把Map当Set用
//主线程赋值时,会获取主线程的holderMap,然后把TTL存进去
holder.get().put((TransmittableThreadLocal<Object>) this, null);
}
}
@Override
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && null == value) {
remove();
} else {
super.set(value);
//当主线程赋值时,会将自己的TTL放到自己的map中
addThisToHolder();
}
}
@Override
public final T get() {
T value = super.get();
if (disableIgnoreNullValueSemantics || null != value)
addThisToHolder();
return value;
}
4.2.3, encapsulación TTL de tareas
//我们通过TtlExecutors.getTtlExecutorService()对线程池进行封装
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) {
return executorService;
}
//入参是线程池,通过包装类代理线程池的操作
return new ExecutorServiceTtlWrapper(executorService);
}
//ExecutorServiceTtlWrapper.submit()
public Future<?> submit(@NonNull Runnable task) {
//将提交的任务进行封装
return executorService.submit(TtlRunnable.get(task));
}
4.2.3.1 Construcción de tareas
Constructor TtlRunnable
El subproceso principal está operando aquí, porque la tarea es enviada por el subproceso principal
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
Aquí hay un atributo clave capturadoRef, que es una referencia atómica y guarda TTL
//TrasmitableThreadLocal.Transmitter
public static Object capture() {
//获取ttl的值构建快照
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
//将主线程TTL的值存到当前任务中
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
4.2.3.2, ejecución de tareas
El código para la ejecución de la tarea es el siguiente: ThreadLocal se reproduce antes de la ejecución de la tarea y ThreadLocal se restaura después de la ejecución de la tarea:
Aquí están todos los subprocesos operativos, porque las tareas las realizan subprocesos
@Override
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!");
}
//1、备份子线程ThreadLocal
//2、使用主线程提交任务时构建的ThreadLocal副本,将子线程ThreadLocal覆盖
Object backup = replay(captured);
try {
//3、任务执行
runnable.run();
} finally {
//3、使用之前备份的子线程ThreadLocal进行恢复
restore(backup);
}
}
En resumen, la idea central de TTL para permitir que el subproceso secundario perciba los cambios del subproceso principal es que el subproceso principal crea una copia ThreadLocal cuando se envía la tarea y la usa cuando el subproceso ejecuta la tarea.
⚠️ El envío y la ejecución de tareas realizarán varias operaciones en TTL, lo que teóricamente tiene un pequeño impacto en el rendimiento. La conclusión oficial de la prueba de rendimiento dice que la pérdida es insignificante
Prueba de rendimiento oficial TTL
RustDesk 1.2: uso de Flutter para reescribir la versión de escritorio, compatible con la supuesta fuga de arquitectura del modelo GPT-4 de Wayland: contiene 1,8 billones de parámetros, utilizando un modelo experto mixto (MoE) Musk anunció el establecimiento de la empresa xAI deepin V23 adaptó con éxito las afirmaciones del proyecto WSL CentOS " Abierto a todos" Rust 1.71.0 Stable Release React ¿Está teniendo un momento Angular.js? Microsoft lanza una nueva fuente predeterminada, Aptos, para reemplazar CalibriMicrosoft : aumentar los esfuerzos para usar la versión Rust IntelliJ IDEA 2023.1.4 en Windows 11Autor: JD Logística Liu Chaoyong
Fuente: propias palabras del desarrollador de JD Cloud