ThreadLocal Java multithread

ThreadLocal

ThreadLocal é fornecido pelo pacote JDK, que fornece variáveis ​​locais de thread, ou seja, se você criar uma variável ThreadLocal, cada thread que acessa essa variável terá uma cópia local dessa variável . Quando vários threads manipulam essa variável, eles realmente manipulam as variáveis ​​em sua própria memória local, evitando assim problemas de segurança de thread. Depois de criar uma variável ThreadLocal, cada thread copiará uma variável para sua própria memória local.

O papel do ThreadLocal (de que adianta)?

Problemas de simultaneidade são particularmente propensos a ocorrer quando vários threads acessam a mesma variável compartilhada, especialmente quando vários threads precisam gravar em uma variável compartilhada. Para garantir a segurança do thread, os usuários gerais precisam realizar a sincronização adequada ao acessar variáveis ​​compartilhadas. A medida de sincronização geralmente é adicionar bloqueios. Isso requer que os usuários tenham um certo conhecimento sobre bloqueios, o que obviamente aumenta a carga sobre os usuários. Então, existe uma maneira de fazer isso, quando uma variável é criada, quando cada thread a acessa, é a variável de seu próprio thread? Na verdade, ThreadLocal pode fazer isso, embora ThreadLocal não se destina a resolver isso. O problema surge.

O princípio de ThreadLocal

Primeiro, observe a estrutura do diagrama de classes das classes relacionadas a ThreadLocal:
Insira a descrição da imagem aquina figura, há um threadLocals e um inheritableThreadLocals na classe Thread , que são variáveis ​​do tipo ThreadLocalMap, e ThreadLocalMap é um Hashmap personalizado . Por padrão, essas duas variáveis ​​em cada encadeamento são nulas e serão criadas apenas quando o encadeamento atual chamar o método ThreadLocal set ou get pela primeira vez . Na verdade, as variáveis ​​locais de cada thread não são armazenadas na instância ThreadLocal, mas na variável threadLocals do thread de chamada. Em outras palavras, as variáveis ​​locais do tipo ThreadLocal são armazenadas em um espaço de memória de thread específico. ThreadLocal é um shell de ferramenta. Ele coloca o valor do valor em threadLocals do thread de chamada por meio do método set e o armazena. Quando o thread de chamada chama seu método get, ele é usado a partir da variável threadLocals do thread atual . Se o thread de chamada nunca termina, então esta variável local sempre será armazenada na variável threadLocals do thread de chamada, então quando você não precisa usar a variável local, você pode chamar o método remove da variável ThreadLocal para deletar o local variável do threadLocals do segmento atual. 另外,Thread里面的threadLocals为何被设计为map结构?很明显是因为每个线程可以关联多个ThreadLocal变量.

Insira a descrição da imagem aqui

A seguir, analisa brevemente a lógica de implementação dos métodos set, get e remove do ThreadLocal.

definir método

Code (1) Primeiro obtenha o thread de chamada e, em seguida, use o thread atual como um parâmetro para chamar o método getMap (t). Você pode ver que a função de getMap (t) é obter a própria variável threadLocals do thread, e A variável threadlocal está ligada à variável membro do thread em.
Se o valor de retorno de getMap (t) não estiver vazio, defina o valor como threadLocals, ou seja, coloque o valor da variável atual na variável de memória threadLocals do segmento atual. ThreadLocals é uma estrutura HashMap, em que key é a referência de objeto da instância ThreadLocal atual e value é o valor passado pelo método set. Se getMap (t) retornar um valor nulo, significa que o método set é chamado pela primeira vez e a variável threadLocals do thread atual é criada neste momento.
// set()方法
public void set(T value) {
    
    
	// (1)获取当前线程
    Thread t = Thread.currentThread();
    // (2)将当前线程作为key,去查找对应的线程变量,找到则设置
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
    	// (3)第一次调用就创建当前线程对应的HashMap
        createMap(t, value);
}

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

// createMap()方法
void createMap(Thread t, T firstValue) {
    
    
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

método get ()

public T get() {
    
    
	// (4)获取当前线程
   Thread t = Thread.currentThread();
   //(5)获取当前线程的threadLocals变量
   ThreadLocalMap map = getMap(t);
   //(6)如果threadLocals不为null,则返回对应本地变量的值
   if (map != null) {
    
    
       ThreadLocalMap.Entry e = map.getEntry(this);
       if (e != null) {
    
    
           @SuppressWarnings("unchecked")
           T result = (T)e.value;
           return result;
       }
   }
   // ( 7) threadLocals为空则初始化当前线程的threadLocals成员变量
   return setInitialValue();
}

// setInitialValue()
private T setInitialValue() {
    
    
	// (8)初始化为null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // (9)如果当前线程的threadLocals变量不为空
    if (map != null)
        map.set(this, value);
    else
    	//(10)如果当前线程的threadLocals变量为空
        createMap(t, value);
    return value;
}

// initialValue()
protected T initialValue() {
    
    
   return null;
}

método remove ()

public void remove() {
    
    
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

InheritableThreadLocal

ThreadLocal não suporta herança, ou seja, depois que a mesma variável ThreadLocal é definida no thread pai, ela não pode ser obtida no thread filho. Para resolver esse problema, InheritableThreadLocal surgiu. InheritableThreadLocal herda de ThreadLocal, que fornece um recurso que permite que threads filho acessem variáveis ​​locais definidas no thread pai. Vamos dar uma olhada no código de InheritableThreadLocal.

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    
	//(1)
	protected T childValue(T parentValue) {
    
    
        return parentValue;
    }
	//(2)
	ThreadLocalMap getMap(Thread t) {
    
    
       return t.inheritableThreadLocals;
    }
	//(3)
	void createMap(Thread t, T firstValue) {
    
    
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

Como pode ser visto no código acima, InheritableThreadLocal herda ThreadLocal e reescreve três métodos. No código (3), podemos ver que InheritableThreadLocal sobrescreve o método createMap.Agora, quando o método set é chamado pela primeira vez, ele cria uma instância da variável inheritableThreadLocals do thread atual em vez de threadLocals. O código (2) mostra que quando o método get é chamado para obter a variável do mapa dentro do thread atual, ele obtém inheritableThreadLocals em vez de threadLocals.

Problema de vazamento de memória ThreadLocal

Na verdade, a chave usada em ThreadLocalMap é uma referência fraca de ThreadLocal.O recurso de referência fraca é que, se houver apenas uma referência fraca para este objeto, ele será inevitavelmente limpo durante a próxima coleta de lixo.

Portanto, se o ThreadLocal não for fortemente referenciado externamente, ele será limpo durante a coleta de lixo, para que a chave que usa este ThreadLocal no ThreadLocalMap também seja limpa. No entanto, o valor é uma referência forte e não será limpo. Dessa forma, um valor com uma chave nula aparecerá.

Esta situação foi considerada na implementação de ThreadLocalMap.Quando os métodos set (), get () e remove () são chamados, os registros cuja chave é nula serão apagados. Se houver vazamento de memória, será apenas quando o método remove () não for chamado manualmente depois que o registro com a chave for nulo e os métodos get (), set () e remove () não forem mais chamados posteriormente .

ThreadLocalRandom 类

Sabemos que a classe Random nos ajuda a gerar números aleatórios. Por exemplo, para gerar vários inteiros do tipo int, usaremos o método nextInt (). Em um único thread, não há problema. E se for multi-threaded? Vamos dar uma olhada no código-fonte de nextInt () primeiro:

protected int next(int bits) {
    
    
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
    
    
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

Pode-se observar que a geração de um novo número aleatório requer duas etapas: ·

  • Primeiro, gere novas sementes com base nas sementes antigas.
  • Em seguida, calcule um novo número aleatório com base na nova semente.

Devido ao oldseed = seed.get();problema de atomicidade, no caso de alta simultaneidade, as sementes antigas obtidas por vários threads podem ser as mesmas, e então as novas sementes geradas são as mesmas, e o resultado final é que os números aleatórios gerados também são os mesmos.

Para compensar as deficiências de Random no caso de alta simultaneidade multi-threaded, a classe ThreadLocalRandom foi adicionada ao pacote JUC.

Primeiro, olhe para a estrutura do diagrama de classes de ThreadLocalRandom:

! [Insira a descrição da imagem aqui] (https://img-blog.csdnimg.cn/20210125192430649.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG70,FF80LmNzize,FF70

Pode-se ver na figura que a classe ThreadLocalRandom herda a classe Random e substitui o método nextInt.A variável de semente atômica herdada da classe Random não é usada na classe ThreadLocalRandom. Não há semente específica armazenada em ThreadLocalRandom. A semente específica é armazenada na variável threadLocalRandomSeed do thread de chamada específico. ThreadLocalRandom é semelhante à classe ThreadLocal, que é uma classe de ferramenta. Quando um thread chama o método atual de ThreadLocalRandom, ThreadLocalRandom é responsável por inicializar a variável threadLocalRandomSeed do thread de chamada, que é a semente de inicialização.

Quando o método nextInt de ThreadLocalRandom é chamado, a variável threadLocalRandomSeed do encadeamento atual é realmente obtida como a semente atual para calcular a nova semente e, em seguida, a nova semente é atualizada para a variável threadLocalRandomSeed do encadeamento atual e, em seguida, o número aleatório é calculado de acordo com a nova semente e usando um algoritmo específico. Deve-se observar aqui que a variável threadLocalRandomSeed é uma variável longa comum na classe Thread,
não é uma variável atômica. Na verdade, o motivo é muito simples, porque essa variável é de nível de thread, portanto, não há necessidade de usar variáveis ​​atômicas, se você ainda não entendeu o princípio de ThreadLocal.

Acho que você gosta

Origin blog.csdn.net/weixin_44533129/article/details/113111127
Recomendado
Clasificación