De TL, ITL a TTL | Equipo técnico de JD Cloud

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

Autor: JD Logística Liu Chaoyong

Fuente: propias palabras del desarrollador de JD Cloud

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 11
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10088879
Recomendado
Clasificación