Fale sobre a compreensão do ThreadLocal

 No módulo multi-threaded do java, ThreadLocal é um ponto de conhecimento que é frequentemente perguntado. Existem muitas maneiras de fazer perguntas. Pode ser um passo a passo ou pode ser apenas como o meu tópico. Portanto, apenas uma abordagem completa compreensão, não importa como você pergunte, todos podem fazer bem.

 Este artigo analisa e compreende principalmente a partir das seguintes perspectivas

 1. O que é ThreadLocal

 2. Como usar ThreadLocal

 3. Análise do código-fonte ThreadLocal

 4. Problema de vazamento de memória ThreadLocal


O que é ThreadLocal?

 Pelo nome, podemos ver que ThreadLocal é chamada de variável de thread, o que significa que a variável preenchida em ThreadLocal pertence ao thread atual e a variável é isolada de outros threads. ThreadLocal cria uma cópia da variável em cada thread, para que cada thread possa acessar sua própria variável de cópia interna.

 É muito fácil de entender pelo significado literal, mas do ponto de vista do uso real não é tão fácil. Como uma entrevista frequentemente feita, os cenários de uso também são bastante ricos:

1. Ao passar objetos entre camadas, o uso de ThreadLocal pode evitar várias passagens e quebrar as restrições entre as camadas.
2. Isolamento de dados entre threads
3. Execute operações de transação para armazenar informações de transação de thread.
4. Conexão de banco de dados, gerenciamento de sessão de sessão.

Como usar ThreadLocal?

Como o papel do ThreadLocal é criar uma cópia para cada thread, usamos um exemplo para verificar:

    public static void main(String[] args) {
    
    

        //新建一个threadLocal
        ThreadLocal<String>local = new ThreadLocal<>();
        //新建一个随机数
        Random random = new Random();
        //利用java8的stream新建5个线程
        IntStream.range(0,5).forEach(a -> new Thread(() -> {
    
    
            //为每一个线程设置相应的local值
            local.set(a+ "  " + random.nextInt(10) );
            System.out.println("线程和local值分别是: "+ local.get());
            try{
    
    
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }).start() );
    }
//        线程和local值分别是: 0  0
//        线程和local值分别是: 3  0
//        线程和local值分别是: 4  2
//        线程和local值分别是: 2  6
//        线程和local值分别是: 1  3

 A partir dos resultados, podemos ver que cada thread tem seu próprio valor local. Definimos um tempo de espera para que outro thread possa ler o valor local atual a tempo.

 Este é o uso básico do TheadLocal, é muito simples? Então, por que ele é mais usado ao se conectar ao banco de dados?

Quando usamos o banco de dados, primeiro estabelecemos uma conexão com o banco de dados e, em seguida, fechamos quando esgotamos. Isso tem um problema muito sério. Se um cliente usa o banco de dados com frequência, precisamos estabelecer várias conexões e fechá-lo. Nosso servidor pode estar sobrecarregado, o que devemos fazer? Se houver 10.000 clientes, a pressão do servidor será ainda maior.

ThreadLocal é melhor neste momento, porque ThreadLocal cria uma cópia da conexão em cada thread e pode ser usado em qualquer lugar dentro do thread, e os threads não afetam uns aos outros, portanto, não há problema de segurança de thread e não Afeta seriamente o desempenho de execução do programa. É muito útil?

O acima exposto é principalmente para explicar um caso básico e, em seguida, analisar porque ThreadLocal é usado ao conectar-se ao banco de dados. Vamos analisar o princípio de funcionamento do ThreadLocal da perspectiva do código-fonte.

Análise de código fonte ThreadLocal

No primeiro exemplo, apenas dois métodos são fornecidos, ou seja, métodos get e set.Na verdade, existem alguns mais que precisam de nossa atenção.

  1. definir método

     /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
          
          
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    A partir do método set, podemos ver que o thread atual t é obtido primeiro e, em seguida, getMap é chamado para obter o ThreadLocalMap. Se o mapa existir, o objeto de thread atual t é usado como a chave e o objeto a ser armazenado é armazenado no mapa como o valor. Se o mapa não existir, inicialize um.

    OK, neste ponto, acredito que você terá algumas dúvidas, o que é ThreadLocalMap e como o método getMap é implementado. Com essas perguntas, continue olhando para baixo. Primeiro, olhe para ThreadLocalMap.

        static class ThreadLocalMap {
          
          
    
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
          
          
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
          
          
                super(k);
                value = v;
            }
        }
    

    Podemos ver que ThreadLocalMap é na verdade uma classe interna estática de ThreadLocal, que define uma Entry para salvar dados e também é uma referência fraca herdada. Use ThreadLocal como a chave dentro de Entry e use o valor que definimos como o valor.

    Também existe um getMap

    ThreadLocalMap getMap(Thread t) {
          
          
    
    return t.threadLocals;
    
    }
    
    

    Chame o segmento atual t e retorne a variável membro threadLocals no segmento atual t. E threadLocals é, na verdade, ThreadLocalMap.

  2. obter método

        /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
          
          
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
          
          
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
          
          
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    

    Por meio da introdução de ThreadLocal acima, acredito que você tenha um bom entendimento deste método. Primeiro obtenha o thread atual e, em seguida, chame o método getMap para obter um ThreadLocalMap. Se o mapa não for nulo, use o thread atual como a entrada chave do ThreadLocalMap e, em seguida, o valor Como o valor correspondente, se não, defina um valor inicial.

    Como definir um valor inicial?

        /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
          
          
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
  3. O método remove
    pode ser removido de nosso mapa.

        /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
          
          
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
    

(1) Cada Thread mantém uma referência para ThreadLocalMap

(2) ThreadLocalMap é uma classe interna de ThreadLocal, usando Entry para armazenar

(3) A cópia criada por ThreadLocal é armazenada em seu próprio threadLocals, que é seu próprio ThreadLocalMap.

(4) O valor-chave de ThreadLocalMap é um objeto ThreadLocal e pode haver várias variáveis ​​threadLocal, portanto, são armazenadas no mapa

(5) Antes de fazer get, você deve primeiro definir, caso contrário, uma exceção de ponteiro nulo será relatada.Claro, um também pode ser inicializado, mas o método initialValue () deve ser reescrito.

(6) O próprio ThreadLocal não armazena o valor, ele apenas serve como uma chave para permitir que o thread obtenha o valor do ThreadLocalMap.

Vários outros pontos a serem observados no ThreadLocal

Insira a descrição da imagem aqui
Qualquer artigo que introduz o ThreadLocal o ajudará a entender um ponto, ou seja, o problema de vazamento de memória. Vamos primeiro dar uma olhada na imagem abaixo.

A imagem acima revela a relação entre ThreadLocal e Thread e ThreadLocalMap em detalhes.

1. Há um mapa no Thread, que é ThreadLocalMap

2. A chave de ThreadLocalMap é ThreadLocal e o valor é definido por nós mesmos.

3. ThreadLocal é uma referência fraca.Quando for nula, será tratada como lixo.

4. O ponto é aqui. De repente, nosso ThreadLocal é nulo, o que significa que será reciclado pelo coletor de lixo. Mas, neste momento, nosso ThreadLocalMap tem o mesmo ciclo de vida do Thread. Ele não será reciclado. Neste momento, há é um fenômeno. Ou seja, a chave de ThreadLocalMap se foi, mas o valor ainda está lá, o que causa um vazamento de memória.

Solução: depois de usar ThreadLocal, execute a operação de remoção para evitar estouro de memória.

Acho que você gosta

Origin blog.csdn.net/woaichihanbao/article/details/107907791
Recomendado
Clasificación