¿Cómo resuelve Spring el problema de concurrencia en Bean multithreading? ----- ThreadLocal

ThreadLocal nace para resolver el problema de los conflictos de acceso de la misma variable, por lo que esta es una solución perfecta para el acceso multiproceso al bean singleton predeterminado de Spring. Spring también usa ThreadLocal para lidiar con los problemas de seguridad de subprocesos de la misma concurrencia variable en múltiples subprocesos.

1. Introducción a ThreadLocal

Luego mire lo que dice jdk: esta clase proporciona variables locales de subproceso. Estas variables son diferentes de las variables ordinarias. Cada subproceso tiene su propia variable, a la que se accede a través del método get o set de ThreadLocal, y tiene una copia inicializada de forma independiente de la variable. La instancia de ThreadLocal suele ser un campo estático privado en la clase que quiere asociar el estado con el hilo.

2. El principio de realización de ThreadLocal

Se proporcionan varios métodos en la clase ThreadLocal:
1.public T get () {}
2.public void set (valor T) {}
3.public void remove () {}
4.protected T initialValue () {}

public T get ()

    public T get() {
    
    
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals变量不为null,就可以在map中查找到本地变量的值
        if (map != null) {
    
    
            //根据ThreadLocal的弱引用的key 查询ThreadLocalMap的Value值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果threadLocals为null,则初始化当前线程的threadLocals变量
        return setInitialValue();
    }

    private T setInitialValue() {
    
    
        //此处是返回一个null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
        if (map != null)
            map.set(this, value);
            //如果threadLocals为null,说明首次添加,需要首先创建出对应的map
        else
            createMap(t, value);
        return value;
    }
    void createMap(Thread t, T firstValue) {
    
    
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

conjunto vacío público (valor T)

//话不多说,这个容易理解
   public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
        //这个是把ThreadLocal当做key,添加的属性值为value
            map.set(this, value);
        else
            createMap(t, value);
    }

public void remove ()

Después de usar ThreadLocal para llamar activamente a este método para evitar pérdidas de memoria

  public void remove() {
    
    
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
         //当前线程根据ThrealLocal为key值删除
             m.remove(this);
     }

protegido T initialValue ()

Al inicializar ThreadLocal, debe anular este método para establecer el valor predeterminado

protected T initialValue() {
    
    
        return null;
    }

Uso de ThreadLocal

1. Echemos un vistazo al ejemplo dado por jdk:

public class ThreadId {
    
    
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
    
    
        @Override
        protected Integer initialValue() {
    
    
            return nextId.getAndIncrement();
        }
    };

    public static int get() {
    
    
        return threadId.get();
    }
}
class TestID{
    
    
    public static void main(String[] args) {
    
    
        int i = ThreadId.get();
        System.out.println(i);//0
    }
}

Si ejecuta este caso, puede ver el resultado de la operación: es 0, porque el initialValue () se reemplaza arriba. Si este método no se reemplaza, el resultado devuelto es nulo.

2. Verifique el aislamiento de los subprocesos:

public class ThreadLocalDemo2 {
    
    
    public static ThreadLocal t1 = new ThreadLocal();
}

class ThreadA extends Thread {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                ThreadLocalDemo2.t1.set("TreadA " + (i + 1));
                System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
class ThreadB extends Thread {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                ThreadLocalDemo2.t1.set("TreadB " + (i + 1));
                System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
class Test {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            ThreadA a = new ThreadA();
            ThreadB b = new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 100; i++) {
    
    
                ThreadLocalDemo2.t1.set("Main " + (i + 1));
                System.out.println("Main get Value " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

Ejecute este caso para ver los resultados: el hilo ThreadA, el hilo ThreadB y el hilo principal obtienen cada uno el valor almacenado por su propio hilo. Pero la llamada es el mismo objeto ThreadLocal, por lo que se concluye que ThreadLocal puede lograr el aislamiento entre subprocesos.

3. La no heredabilidad de ThreadLocal:

En el ejemplo anterior se puede ver que los datos entre el subproceso principal y los subprocesos secundarios están aislados y no se pueden heredar, entonces la subclase de ThreadLocal InheritableThreadLocal proporciona una solución a este problema.
Averigüemos a través de un caso:

public class ThreadLocalDemo6 {
    
    
    public static InheritableThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal() {
    
    
        @Override
        protected Long initialValue() {
    
    
            return new Date().getTime();
        }
    };
}
class ThreadA6 extends Thread {
    
    
    @Override
    public void run() {
    
    
        try {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                System.out.println("在ThreadA 线程中取值 : " + ThreadLocalDemo6.inheritableThreadLocal.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
class Test6 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println("在线程main 中取值为: " + ThreadLocalDemo6.inheritableThreadLocal.get());
        }
        Thread.sleep(5000);
        ThreadA6 threadA6 = new ThreadA6();
        threadA6.start();
    }
}

Ejecute este caso para ver el resultado en ejecución: el valor del atributo establecido por el hilo principal se puede obtener en el hilo secundario, luego se puede concluir que InheritableThreadLocal ha heredado características.

Desde ThreadLocalMap para ver el problema de pérdida de memoria por uso inadecuado de ThreadLocal

Analizar la implementación interna de ThreadLocalMap

El interior de ThreadLocalMap es una matriz de entrada, echemos un vistazo al código fuente de la entrada.

    /**
     * Entry是继承自WeakReference的一个类,该类中实际存放的key是
     * 指向ThreadLocal的弱引用和与之对应的value值(该value值
     * 就是通过ThreadLocal的set方法传递过来的值)
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
        /** value就是和ThreadLocal绑定的 */
        Object value;

        //k:ThreadLocal的引用,被传递给WeakReference的构造方法
        Entry(ThreadLocal<?> k, Object v) {
    
    
            super(k);
            value = v;
        }
    }

Se puede ver en lo anterior que ThreadLocal es una referencia débil, entonces esta referencia no puede resistir un GC, la clave se puede reciclar, luego el valor no se puede reciclar, lo que puede causar una pérdida de memoria (porque en este momento ThreadLocalMap tendrá un clave nula pero un valor no nulo Elemento de entrada).

Uso estándar de ThreadLocal

Después de usar ThreadLocal (set () o get ()), si no está en uso, use el método remove () para limpiar los recursos a tiempo y evitar pérdidas de memoria.

Supongo que te gusta

Origin blog.csdn.net/qq_43565087/article/details/106613341
Recomendado
Clasificación