Fundamentos de diseño de ThreadLocal

¡Acostúmbrate a escribir juntos! Este es el sexto día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

Los estudiantes que conocen la programación de subprocesos múltiples de Java no deben ser ajenos a ThreadLocal. Los estudiantes que han visto el código fuente de ThreadLocal definitivamente elogiarán su diseño. Pasemos al código fuente de ThreadLocal (Java 8) y veamos cómo está diseñado. .

1 ¿Qué es ThreadLocal?

ThreadLocal se refiere a la variable local del hilo. Podemos diseñar una variable a la que solo se puede acceder dentro del hilo a través de ThreadLocal, y la variable está aislada de otros hilos.

2 ¿Qué puede hacer ThreadLocal?

La característica más importante de ThreadLocal es el aislamiento de subprocesos, que no pueden obtener otros subprocesos. Los datos almacenados en ThreadLocal del subproceso actual y luego la información almacenada en ThreadLocal, sin importar qué tan profundo sea el enlace de llamada de su programa, siempre que es el mismo subproceso, no se requiere el paso de parámetros, se puede obtener directamente.

3 Aplicaciones típicas de ThreadLocal

Un escenario de aplicación típico es que la aplicación en el complemento de paginación MyBatis necesita una interfaz de paginación, y la información de paginación debe colocarse primero en ThreadLocal y obtenerse directamente a través de ThreadLocal cuando sea necesario, de modo que no sea necesario pasar cada interfaz. en la información de paginación y luego pásela al complemento de paginación.

imagen.png

En las cosas declarativas de Spring, ThreadLocal también se usa para almacenar información de transacciones, porque solo usamos la misma conexión de base de datos para configurar que las cosas surtan efecto, y el administrador de transacciones de Spring lo vinculará a ThreadLocal después de obtener la conexión de la base de datos. hecho.

4 Análisis de código fuente ThreadLocal

El código fuente de ThreadLocal es relativamente fácil de entender Tomamos el método set de ThreadLocal como entrada y comenzamos a ver su principio.

  • Primero, el método set primero obtendrá el hilo actual t
  • Obtener el objeto ThreadLocalMap del hilo actual a través del hilo actual t
  • Determinar si ThreadLocalMap está vacío
    • Cuando no está vacío, establezca el valor para el mapa, la clave es el objeto ThreadLocal actual y el valor es el parámetro entrante
    • 为空时 调用ThreadLocalMap构造函数初始化对象
public class ThreadLocal<T> {
    ......
        
    // ThreadLocal设置值
	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    // 获取当前线程的ThreadLocalMap对象
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    // 当前线程ThreadLocalMap对象为空时,调用ThreadLocalMap构造函数初始化对象
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    // ThreadLocalMap为ThreadLocal的静态内部类,为Thread的属性
    static class ThreadLocalMap {
        ......
        
    	ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
    	}
    }
    
}
复制代码

通过这一段代码,我们就可以很清楚的分析到ThreadLocal为什么能做到线程间的隔离,因为ThreadLocalMap是Thread的属性,也就是说这些数据是存储在Thread上的,这也是对其名字最好的理解。

然后我们再来看一下这个静态内部类ThreadLocalMap的数据结构,我们可以看到这个Map比HashMap简单许多,它是一个Entry数组,Entry是继承自WeakReference对象的(弱应用,垃圾回收时会清理),并且该Map并没有采用拉链的方式来解决hash冲突,而是拿当前下标,判断寻找下一个符合条件的位置存放。

Entry的类继承关系如下图,其中key是由弱应用,保存在Reference对象中的,通过get()方法获取。

imagen.png

public class ThreadLocal<T> {
    ......
    
    static class ThreadLocalMap {
        
        ......
        
       static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        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++;
                    }
                }
            }
        }
        
        private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
                    
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
        
        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
    }
}   
复制代码

ThreadLocal子类InheritableThreadLocal的设计,它主要是从写其getMap,createMap,childValue方法,使其调用Thread中inheritableThreadLocals属性,inheritableThreadLocals在Thread会继承父线程的inheritableThreadLocals属性,从而实现子父线程间变量的传递。

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);
    }
}
复制代码

5 ThreadLocal 精妙设计

imagen.png

  1. 线程隔离

    ThreadLocal线程本地变量如其名字一样,并没有将数据放在ThreadLocal对象中,而将ThreadLocalMap放在Thread对象上,这样ThreadLocal只是提供操作数据的入口,并不具备实际存储能力,这样就可以做到线程隔离。

  2. 弱引用解决key的内存释放

    ThreadLocal通过弱引用的方式,使得ThreadLocal在外部强引用消失时,可以自动被垃圾回收收集,而不需要去释放ThreadLocalMap中key的引用。

    重点:key可以通过弱引用被释放,那么value如何处理呢?

    Especialmente cuando se usa el grupo de subprocesos, los subprocesos se reutilizarán. Si ThreadLocalMap no se puede reciclar de manera efectiva, es probable que ocurran datos sucios e incluso un desbordamiento de memoria. Cuando terminemos de usar ThreadLocal, debemos llamar a su método remove.

        public static void main(String[] args) {
            new Thread(()-> {
                try {
                    threadLocal.set("hello");
                    threadLocal.get();
                } finally {
                    threadLocal.remove();
                }
            }).start();
        }
    复制代码
  3. Los subprocesos principales y secundarios pasan ThreadLocal

    Aunque ThreadLocal es un hilo aislado, en teoría es posible que un hilo hijo obtenga el valor establecido en el hilo padre, en este momento podemos lograrlo a través de InheritableThreadLocal.

    InheritableThreadLocal es una subclase de ThreadLocal, que reescribe los métodos para crear createMap, getMap, childValue, etc. Hay dos variables miembro en la clase Thread, una es threadLocals y la otra es heritageThreadLocals. Cuando se crea el hilo, se juzga si el subproceso principal tiene ThreadLocals heredables.Algunos de ellos se heredarán, para realizar la transferencia de ThreadLocalMap del subproceso principal al subproceso secundario.

6 casos de prueba de ThreadLocal

La siguiente es la forma más fácil de usar ThreadLocal, puede rastrear el código fuente para verificarlo.

package com.zhj.interview;

public class Test18 {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        inheritableThreadLocal.set("hello main");
        new Thread(()-> {
            try {
                threadLocal.set("hello");
                System.out.println( Thread.currentThread().getName() + threadLocal.get());
                System.out.println( Thread.currentThread().getName() + inheritableThreadLocal.get());
                inheritableThreadLocal.set("hello thread1");
                System.out.println( Thread.currentThread().getName() + inheritableThreadLocal.get());
            } finally {
                threadLocal.remove();
            }
        },"thread1 - ").start();

        new Thread(()-> {
            try {
                Thread.sleep(10);
                System.out.println( Thread.currentThread().getName() + inheritableThreadLocal.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                threadLocal.remove();
            }
        }, "thread2 - ").start();
    }
}
复制代码

Gracias por leer, si lo encuentras útil, por favor dale me gusta, ¡muchas gracias! ! !

Supongo que te gusta

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