O princípio do ThreadLocal e como ele é aplicado no Looper? (ByteDance, Xiaomi)
O que você deseja examinar com esta pergunta?
ThreadLocal deve ser dominado. Isso ocorre porque o princípio de funcionamento do Looper tem muito a ver com ThreadLocal. Compreender a implementação de ThreadLocal nos ajudará a entender o princípio de funcionamento do Looper. Este artigo começará com o uso de ThreadLocal. Acompanhe todos passo a passo. passo para entender o ThrealLocal.
Pontos de conhecimento para investigar
- O funcionamento interno do ThreadLocal
- Conhecimento relacionado ao Looper
Como os candidatos devem responder
ThreadLocal pode salvar um objeto em um thread especificado.Depois que o objeto é salvo, os dados salvos só podem ser obtidos no thread especificado e os dados não podem ser obtidos por outros threads. ThreadLocal raramente é usado no desenvolvimento diário, mas o sistema o utiliza no mecanismo Handler para garantir que cada thread do Handler tenha um objeto Looper independente para entender melhor o mecanismo do Handler.
O que é ThreadLocal
ThreadLocal é uma classe sobre como criar variáveis locais de thread.
Na verdade, o escopo desta variável é o thread e não pode ser acessado por outros threads. Normalmente as variáveis que criamos podem ser acessadas por qualquer thread, mas as variáveis criadas usando ThreadLocal só podem ser acessadas pelo thread atual e não podem ser acessadas por outros threads.
Então, como o ThreadLocal garante que apenas o thread atual possa acessá-lo? Vamos primeiro analisar as duas funções mais importantes em ThreadLocal, get() e set().
Primeiro, observe o código-fonte no método get()
public T get() {
Thread t = Thread.currentThread(); //code 1
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();
}
public void set(T value) {
Thread t = Thread.currentThread(); //code 2
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 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;
}
A partir de code1 e code2, deve ser fácil descobrir que ao definir ou obter o valor de ThreadLocal, o thread atual será obtido primeiro e, em seguida, getMap(thread) será chamado com base no thread. O que getMap retorna é a variável de membro threadLocals do thread thread. Portanto, a função correspondente é executada através de get e set, o que garante que o acesso a threadLocal só possa acessar ou modificar o valor do thread atual, o que garante que esta variável seja uma variável local do thread.
Então, qual é o próximo ThreadLocalMap?
O que é ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
//省略部分代码
}
Pode-se ver no código-fonte que ThreadLocalMap é um array, e o array contém Entry herdado de referências fracas. Referências fracas também são usadas para que a memória possa ser recuperada quando ocorrerem situações anormais, como loops infinitos.
Vamos voltar e dar uma olhada no código fonte de set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
O código aqui é muito claro. Ele retira o ThreadLocalMap de acordo com o thread atual e depois executa a operação de armazenamento de dados. Se o Mapa estiver vazio, ele é criado primeiro e depois atribuído.
Explorar map.set()
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();
}
Int i = key.threadLocalHashCode & (len-1) no código é igual ao método de valor hash da chave no HashMap, principalmente para evitar conflitos de valor hash. Indo mais abaixo, existem três situações ao percorrer o Mapa: 1. Encontrar uma chave existente que seja válida, e então atribuir um valor; 2. Encontrar uma chave existente, mas é inválida, substituir a Entrada expirada; 3. Se a mesma o valor da chave não for encontrado, crie uma nova entrada e atribua um valor.
Então, como o ThreadLocal é usado no Looper?
Aplicação de ThreadLocal no Looper
Encontre o código-fonte do Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//code 1
throw new RuntimeException("Only one Looper may be create per thread");
}
sThreadLocal.set(new Looper(quitAllowed));// code 2
}
O relacionamento entre Looper e threads é um para um. Os objetos Looper são isolados entre threads diferentes. Então, como o Looper garante isso? Através do código acima, deve ser fácil descobrir que a inicialização do Looper deve chamar a função de preparação.Quando a função de preparação é chamada, o código será executado no código 1. No código 1, ele primeiro determinará se o valor do looper existe no ThreadLocal do thread atual. Se existir, então uma exceção é lançada. Esta execução garante que um thread definirá Looper apenas uma vez. O fluxo de execução deste código garante que um thread tenha apenas um ThreadLocal e um ThreadLocal tenha apenas um looper.
Resumir
ThreadLocal é uma classe que cria variáveis locais de thread. Seu mecanismo de implementação determina que o escopo desta variável é o thread e não pode ser acessado por outros threads. Usando este mecanismo, você pode garantir que um thread tenha apenas uma variável ThreadLocal. Então, através do design da função prepare no looper, é garantido que um ThreadLocal será vinculado apenas a um Looper. Através desses dois métodos, é garantido que um thread possua apenas uma variável ThreadLocal, e uma variável ThreadLocal possua apenas um Looper, formando assim uma correspondência um-para-um.
afinal
Eu compilei uma coleção de perguntas de entrevista sobre Android. Além das perguntas de entrevista acima, também inclui [ noções básicas de Java, coleções, multi-threading, máquinas virtuais, reflexão, genéricos, programação simultânea, os quatro componentes principais do Android, tarefas assíncronas e mensagens mecanismos, desenho de UI, ajuste de desempenho, SDN, estrutura de terceiros, padrão de design, Kotlin, rede de computadores, processo de inicialização do sistema, Dart, Flutter, algoritmo e estrutura de dados, NDK, H.264, H.265. Codec de áudio, FFmpeg , OpenMax, OpenCV, OpenGL ES ]