¿Cómo evitar olvidar limpiar ThreadLocal?

1. Antecedentes

ThreadLocal puede resolver el "problema de seguridad de subprocesos".inserte la descripción de la imagen aquí

También se puede utilizar como contexto para almacenar temporalmente datos para pasos posteriores.

Sin embargo, el uso de ThreadLocal es propenso a fallar, por lo que algunos equipos no permiten el uso de ThreadLocal.

Una de las razones principales es que es fácil olvidarse de limpiar y reutilizar en un entorno de grupo de subprocesos conduce a un entorno de cadena.

inserte la descripción de la imagen aquí

Entonces, ¿hay alguna solución elegante? Este artículo da una solución propia.

En segundo lugar, la solución

package basic.thread;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;

public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /**
     * 初始化上下文
     */
    public static void initContext() {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else {
            CONTEXT.get().clear();
        }
    }

    /**
     * 清除上下文
     */
    public static void clearContext() {
        CONTEXT.remove();
    }

    /**
     * 获取上下文内容
     */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /**
     * 设置上下文参数
     */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8));
            con = CONTEXT.get();
        }
        con.put(key, value);
    }
}

复制代码

2.1 Recomendaciones en el Manual de desarrollo de Java

inserte la descripción de la imagen aquí

Escriba lo siguiente:

    public Result<R> executeAbility(T ability) {
           //初始化上下文
            ThreadContext.initContext();
        try {
            //省略核心业务代码

        } finally {
            ThreadContext.clearContext();
        }
    }
复制代码

2.2 Otras mejoras

Creo que la gran mayoría de la gente se detendrá ahí, pero no creo que sea suficiente. ¿Cómo puedo evitar olvidarme de limpiar threadlocal?

¿Hay un caso similar en el código fuente de JDK? Después de pensar en la lectura y escritura de archivos de E/S, también es necesario utilizar un enfoque similar para liberar recursos.JDK proporciona prueba con recursos para facilitar la liberación de recursos.Los usuarios no necesitan escribir manualmente finalmente para liberar recursos.

Caso común: inserte la descripción de la imagen aquíuse probar con recursoinserte la descripción de la imagen aquí

Además, sabemos que podemos personalizar los recursos de prueba con recursos implementando AutoCloseable.

Pero al final, se encontró que no era muy adecuado, porque en el escenario de pasar el contexto, la clase de herramienta ThreadLocal suele ser estática, e incluso si static no es aplicable, no es muy conveniente pasar el objeto cuando conseguir la propiedad.

Por supuesto, si no desea usarlo de forma estática, también puede considerar implementar la interfaz AutoClosebale y usar el mecanismo de prueba con recursos.

¿Podemos usar un mecanismo similar?

Puede privatizar directamente los métodos de inicialización y limpieza, proporcionar encapsulación sin parámetros y devolver valores, usar Runnbale y Callable para pasar llamadas como parámetros y encapsular la lógica de prueba final en métodos encapsulados.

package basic.thread;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /**
     * 初始化上下文
     */
    private static void initContext() {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else {
            CONTEXT.get().clear();
        }
    }

    /**
     * 清除上下文
     */
    private static void clearContext() {
        CONTEXT.remove();
    }

    /**
     * 获取上下文内容
     */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /**
     * 设置上下文参数
     */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8));
            con = CONTEXT.get();
        }
        con.put(key, value);
    }

    /**
     * 自动回收的封装
     */
    public static void  runWithAutoClear(Runnable runnable){
        initContext();
        try{
            runnable.run();
        }finally{
            CONTEXT.remove();
        }
    }

    /**
     * 自动回收的封装
     */
    public static <T> T callWithAutoClear(Callable<T> callable){
        initContext();
        try{
            try {
                return callable.call();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }finally{
            CONTEXT.remove();
        }
    }
}

复制代码

Usar referencia:

   public Result<R> executeAbility(T ability) {
      return  AbilityContext.callWithAutoClear(()->{
        		 // 业务核心代码
       });
    }
复制代码

3. Algunas preguntas

3.1 Por lo general, los contextos de subprocesos se usan en todas las clases, ¿no tiene sentido hacerlo?

Por lo general, la clase de herramienta de contexto de hilo se establece en la capa más externa que necesita usar la herramienta de contexto. También se puede configurar directamente en la capa de implementación de la interfaz de RPC o el método de Controller.

Si toda la llamada involucra varias clases, siempre que se inicie en el mismo subproceso o sea iniciada por el mismo subproceso (usando TransmittableThreadLocal), el método put u get de ThreadContext aún se puede usar en el método llamado por la subfunción o hilo.

4. Resumen

Mientras la mente no se deslice, siempre hay más soluciones que dificultades.

Debemos encontrar una manera de resolver el problema, no evitarlo. Cuando vea que algunas soluciones aún son propensas a errores, debe encontrar formas de realizar mejoras adicionales.

Por supuesto, si no desea utilizar ThreadLocal y desea almacenar objetos temporalmente para su uso posterior, puede definir objetos de contexto y pasarlos entre diferentes pasos de ejecución.

Artículos similares son: "Activar la implementación de un comportamiento específico cuando cambia el mapa"


No es fácil de crear. Si este artículo te es útil, dale me gusta, marca como favorito y sígueme. Tu apoyo y aliento son la mayor motivación para mi creación.inserte la descripción de la imagen aquí

Supongo que te gusta

Origin juejin.im/post/7080167877881364510
Recomendado
Clasificación