Aprenda um pouco de multithreading todos os dias

Multithreading

1. Conceitos relacionados

Simultaneidade e Paralelismo

Paralelo (paralelo): refere-se a várias tarefas de evento ocorrendo ao mesmo tempo (simultaneamente).
Simultaneidade : Refere-se a dois ou mais eventos que ocorrem dentro do mesmo pequeno período de tempo .A execução simultânea de programas pode fazer uso total dos recursos da CPU em condições limitadas.
insira a descrição da imagem aqui
CPU single-core: apenas simultânea
CPU multi-core: paralela + simultânea

Tópicos e Processos

  • Programa : Para concluir uma determinada tarefa e função, escolha um conjunto de instruções escritas em uma linguagem de programação.

  • Software : 1 ou mais aplicativos + materiais relacionados e arquivos de recursos constituem um sistema de software.

  • Um processo é uma descrição do processo em execução (criação-execução-morte) de um programa.O sistema cria um processo para cada programa em execução e aloca recursos de sistema independentes, como espaço de memória, para o processo.

  • Thread : Um thread é uma unidade de execução em um processo, que é responsável por concluir a tarefa de executar o programa atual.Existe pelo menos um thread em um processo. Pode haver vários encadeamentos em um processo, e esse programa aplicativo também pode ser chamado de programa multiencadeado neste momento. O multi-threading permite que os programas sejam executados simultaneamente e façam uso total dos recursos da CPU.

    Pergunta da entrevista : Um processo é a menor unidade de agendamento do sistema operacional e alocação de recursos, e um thread é a menor unidade de agendamento da CPU. Diferentes processos não compartilham memória. O custo de troca de dados e comunicação entre processos é alto. Diferentes threads compartilham a memória do mesmo processo. Obviamente, threads diferentes também têm seu próprio espaço de memória independente. Para a área de método, a memória do mesmo objeto no heap pode ser compartilhada entre as threads, mas as variáveis ​​locais da pilha são sempre independentes.

Vantagens e cenários de aplicação do multithreading

  • A principal vantagem:
    • Faça uso total das fatias de tempo ocioso da CPU para concluir as solicitações do usuário no menor tempo possível. Isso é para fazer o programa responder mais rápido.
  • Cenário de aplicação:
    • multitarefa. Quando vários usuários solicitam o servidor, o programa do servidor pode abrir vários threads para processar a solicitação de cada usuário separadamente, sem afetar uns aos outros.
    • Processamento de tarefa grande única. Para baixar um arquivo grande, você pode abrir vários threads para baixar juntos, reduzindo o tempo geral de download.

agendamento de encadeamento

Refere-se a como os recursos da CPU são alocados para diferentes threads. Dois métodos comuns de escalonamento de threads:

  • agendamento de compartilhamento de tempo

    Todos os threads se revezam para usar a CPU e cada thread ocupa o tempo da CPU uniformemente.

  • agendamento preventivo

    Dê prioridade a threads com alta prioridade para usar a CPU.Se as threads tiverem a mesma prioridade, uma será selecionada aleatoriamente (aleatoriedade de thread).Java usa um método de escalonamento preemptivo .

2. Criação e inicialização do thread

Herdar a classe Thread

Etapas para criar e iniciar multithreading herdando a classe Thread:

  1. Defina a subclasse da classe Thread e reescreva o método run() dessa classe. O corpo do método run() representa a tarefa que a thread precisa concluir, então o método run() é chamado de corpo de execução da thread .
  2. Crie uma instância da subclasse Thread, ou seja, crie um objeto thread
  3. Chame o método start() do objeto thread para iniciar o thread

Notas sobre a análise de execução multi-threaded :
insira a descrição da imagem aqui

  • Chamar manualmente o método run não é a maneira de iniciar um thread, é apenas uma chamada de método normal.

  • Após o método start iniciar o thread, o método run será chamado e executado pela JVM.

  • Não inicie o mesmo thread repetidamente, caso contrário, uma exceção será lançadaIllegalThreadStateException

  • Não use a unidade Junit para testar multi-threading, não é suportado, após o término do thread principal, ele chamará para System.exit()sair da JVM diretamente;

Implemente a interface Runnable

  1. Defina a classe de implementação da interface Runnable e reescreva o método run() da interface. O corpo do método run() também é o corpo de execução do thread.
  2. Crie uma instância da classe de implementação Runnable e use essa instância como o destino de Thread para criar um objeto Thread, que é o objeto thread real.
  3. Chame o método start() do objeto thread para iniciar o thread.

Comparação de duas formas de criar threads

  • A própria classe Thread também implementa a interface Runnable.O método run vem da interface Runnable, e o método run também é a tarefa de thread real a ser executada.

    public class Thread implements Runnable {
          
          }
    
  • Como as classes Java são herdadas de forma única, a maneira de herdar Thread tem a limitação de herança única, mas é mais simples de usar.

  • A maneira de implementar a interface Runnable evita a limitação de herança única e pode criar vários objetos de threadCompartilhar um objeto de classe de implementação Runnable (classe de tarefa de encadeamento), de forma a facilitar a execução de tarefas multi-threadedcompartilhar dados.

Thread de criação de objeto de classe interna anônima

Criar threads por meio de objetos de classe interna anônimos não é uma nova maneira de criar threads, mas quando a tarefa de thread precisa ser executada apenas uma vez, não precisamos criar classes de thread separadamente, podemos usar objetos anônimos:

new Thread("新的线程!"){
    
    
	@Override
	public void run() {
    
    
		for (int i = 0; i < 10; i++) {
    
    
			System.out.println(getName()+":正在执行!"+i);
		}
	}
}.start();

new Thread(new Runnable(){
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+":" + i);
        }
    }
}).start();

3. Classe de rosca

método de construção

  • public Thread() : Aloca um novo objeto de thread.
  • public Thread(String name): Aloca um novo objeto de thread com o nome especificado.
  • public Thread(Runnable target): Aloca um novo objeto de thread com o alvo especificado.
  • public Thread(Runnable target, String name): Aloca um novo objeto de thread com o alvo especificado e especifica o nome.

Threads usam o método básico

  • public void run() : A tarefa a ser executada por este thread é definida aqui.

  • public String getName() : Obtém o nome do thread atual.

  • public static Thread currentThread() : Retorna uma referência ao objeto thread em execução no momento.

  • public final boolean isAlive(): Testa se a thread está ativa. Ativo se o thread foi iniciado e ainda não foi encerrado.

  • public final int getPriority() : retorna a prioridade do thread

  • public final void setPriority(int newPriority): altera a prioridade do thread

    • Cada thread tem uma certa prioridade e threads com prioridade mais alta terão mais oportunidades de execução. Por padrão, cada thread tem a mesma prioridade que o thread pai que o criou. A classe Thread fornece as classes de método setPriority(int newPriority) e getPriority() para definir e obter a prioridade do thread, onde o método setPriority requer um número inteiro e o intervalo está entre [1,10]. defina as três prioridades das constantes da classe Thread:
    • MAX_PRIORITY (10): prioridade mais alta
    • MIN_PRIORITY (1): prioridade mais baixa
    • NORM_PRIORITY (5): prioridade normal, por padrão a thread principal tem prioridade normal.

Métodos comuns de controle de encadeamento

  • public void start() : Faz com que este thread comece a executar; a máquina virtual Java chama o método run deste thread.

  • public static void sleep(long millis): Thread sleep, que faz com que o thread atualmente em execução pare (interrompa temporariamente a execução) pelo número especificado de milissegundos.

  • public static void yield(): Polidez do encadeamento, rendimento apenas faz com que o encadeamento atual perca temporariamente o direito de executar, permite que o agendador de encadeamento do sistema reagende, esperando que outros encadeamentos com a mesma ou maior prioridade que o encadeamento atual possam ter a chance de executar , mas isso não é garantido. É totalmente possível que quando um thread chama o método yield para pausar, o agendador de threads agende-o para reexecução.

  • void join() : Junte-se a um thread, adicione um novo thread ao thread atual, espere o thread unido terminar antes de continuar a executar o thread atual.

    void join(long millis): espera até que este thread termine por até millis milissegundos. Se o tempo de milésimos acabar, não haverá mais espera.

    void join(long millis, int nanos): Aguarde até que o thread termine por até millis millis + nanos nanossegundos.

  • public final void stop(): Força o thread a parar de executar. Este método é inseguro, obsoleto e não deve ser usado.

    • Chamar o método stop() interromperá imediatamente todo o trabalho restante no método run(), incluindo aqueles na instrução catch ou finalmente, e lançará uma exceção ThreadDeath (geralmente essa exceção não requer uma captura explícita), portanto, pode causar alguns O trabalho de limpeza não pode ser concluído, como fechar arquivos, bancos de dados, etc.
    • Chamar o método stop() liberará imediatamente todos os bloqueios mantidos pelo thread, resultando em dados não sincronizados e inconsistência de dados.
  • public void interrupt(): Interromper o encadeamento na verdade marca o encadeamento como uma interrupção e não interrompe a execução do encadeamento.

  • public static boolean interrupt(): Verifique o status de interrupção do thread, chamando este método irá limpar o status de interrupção (flag).

  • public boolean isInterrupted(): Verifica o status do thread interrompido, não irá limpar o status interrompido (sinalizador)

  • public void setDaemon(boolean on): define a thread como uma thread daemon. Deve ser definido antes do início do thread, caso contrário, IllegalThreadStateExceptionuma exceção será relatada.

    • A thread daemon serve principalmente a outras threads. Quando não há nenhuma thread não-daemon em execução no programa, a thread daemon também encerrará a execução. O coletor de lixo JVM também é um encadeamento daemon.
  • public boolean isDaemon(): Verifique se a thread atual é uma thread daemon.

  • O papel de volátil é garantir que algumas instruções não sejam omitidas devido à otimização do compilador. A variável volátil significa que a variável pode ser alterada inesperadamente. Releia cuidadosamente o valor dessa variável todas as vezes, em vez de usar salvar um backup no registrar, para que o compilador não assuma o valor dessa variável.

ciclo de vida do fio

Cinco estados de rosca do modelo de rosqueamento tradicional
insira a descrição da imagem aqui

Os seis estados de encadeamento definidos pelo JDK definem uma classe de enumeração dentro da classe para descrever os seis estados do encadeamento
:java.lang.Thread

    public enum State {
    
    
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

insira a descrição da imagem aqui
Descrição: Ao retornar de WAITINGou para o estado, se for constatado que o thread atual não obteve o bloqueio do monitor, ele será imediatamente transferido para o estado.TIMED_WAITINGRunnableBLOCKED

segurança do fio

Quando utilizamos várias threads para acessar o mesmo recurso (pode ser a mesma variável, o mesmo arquivo, o mesmo registro etc.), mas se houver operações de leitura e gravação no recurso em várias threads, haverá inconsistência de dados problemas antes e depois, que é um problema de segurança do thread.

Problemas de segurança de rosca levam a

  • Variáveis ​​locais não podem ser compartilhadas : variáveis ​​locais são independentes cada vez que o método é chamado, então os dados de run() de cada thread são independentes, não dados compartilhados.
  • As variáveis ​​de instância de diferentes objetos não são compartilhadas : as variáveis ​​de instância de diferentes objetos de instância são independentes.
  • variáveis ​​estáticas são compartilhadas
  • Variáveis ​​de instância do mesmo objeto são compartilhadas

Resumo: problemas de segurança de rosca surgem devido às seguintes condições

  1. execução multithread
  2. compartilhar dados
  3. Várias instruções operam em dados compartilhados

Solução para o problema de segurança da rosca

insira a descrição da imagem aqui
Método de sincronização : A palavra-chave sincronizada modifica diretamente o método, indicando que apenas um encadeamento pode entrar nesse método ao mesmo tempo e outros encadeamentos estão esperando do lado de fora.

public synchronized void method(){
    
    
    可能会产生线程安全问题的代码
}

Bloco de código sincronizado : A palavra-chave sincronizado pode ser usada na frente de um determinado bloco, indicando que apenas os recursos desse bloco são mutuamente exclusivos.

synchronized(同步锁){
    
    
     需要同步操作的代码
}

Bloquear seleção de objeto

preferiu issoseguido pelaclasse.classepode ser também" "objeto string vazio

Objeto de bloqueio de sincronização:

  • Objetos de bloqueio podem ser de qualquer tipo.
  • Vários objetos de encadeamento usam o mesmo bloqueio.

O objeto de bloqueio do bloco de código sincronizado

  • No bloco de código estático: use o objeto Class da classe atual
  • Em blocos de código não estáticos: é comum considerar isso primeiro, mas preste atenção se o mesmo

O escopo do bloqueio é muito pequeno: ele não pode resolver problemas de segurança e todas as instruções que operam em recursos compartilhados devem ser sincronizadas.

O escopo do bloqueio é muito grande: porque uma vez que um thread agarra o bloqueio, outros threads só podem esperar, então o escopo é muito grande, a eficiência será reduzida e os recursos da CPU não poderão ser usados ​​razoavelmente.

Problemas de segurança de encadeamento do padrão de design singleton

1. O estilo chinês faminto não tem problemas de segurança de rosca

Estilo chinês faminto: crie objetos assim que aparecer

2. Problemas de segurança de encadeamento de estilo preguiçoso

public class Singleton {
    
    
    private static Singleton ourInstance;

    public static Singleton getInstance() {
    
    
        //一旦创建了对象,之后再次获取对象,都不会再进入同步代码块,提升效率
        if (ourInstance == null) {
    
    
            //同步锁,锁住判断语句与创建对象并赋值的语句
            synchronized (Singleton.class) {
    
    
                if (ourInstance == null) {
    
    
                    ourInstance = new Singleton();
                }
            }
        }
        return ourInstance;
    }

    private Singleton() {
    
    
    }
}

Aguardando o mecanismo de despertar

Quando um thread atende a uma determinada condição, ele entra no estado de espera ( wait() / wait(time) ), espera que outros threads executem seu código especificado e, em seguida, o ativa ( notify() ); ou você pode especificar a espera time , espere o tempo acordar automaticamente; quando houver vários threads esperando, se necessário, você pode usar notifyAll() para acordar todos os threads em espera. wait/notify é um mecanismo de coordenação entre threads.

  1. wait: A thread não está mais ativa, não participa mais do escalonamento e entra no conjunto de espera, portanto não desperdiçará recursos da CPU e não competirá por bloqueios. O estado da thread neste momento é WAITING ou TIMED_WAITING. Ele também espera que outras threads executem uma ação especial , ou seja, " notificar " ou quando o tempo de espera acabar, a thread que está esperando por este objeto é liberada do wait set e entra novamente na fila de dispatch (ready queue) ) meio
  2. notificar: selecione um thread no conjunto de espera do objeto notificado para liberar;
  3. notifyAll: Libera todos os encadeamentos no conjunto de espera do objeto notificado.

Obs:
Após a thread notificada ser acordada, ela pode não conseguir retomar a execução imediatamente, pois o local onde foi interrompida era no bloco de sincronização, e neste momento ela não está mais segurando o lock, então ela precisa tentar adquirir o bloqueio novamente (provavelmente enfrentando outra concorrência do Thread), somente após o sucesso a execução pode ser retomada no local após o qual o método wait foi originalmente chamado.

Resumido da seguinte forma:

  • Se o bloqueio puder ser adquirido, o thread muda do estado WAITING para o estado RUNNABLE (executável);
  • Caso contrário, a thread muda do estado WAITING para o estado BLOCKED (aguardando bloqueio).

Os detalhes que precisam ser observados ao chamar os métodos wait e notify

  1. O método wait e o método notify devem ser chamados pelo mesmo objeto de bloqueio. Porque: o objeto de bloqueio correspondente pode ativar o thread após o método wait chamado com o mesmo objeto de bloqueio por meio de notificação.
  2. O método wait e o método notify pertencem aos métodos da classe Object. Porque: o objeto de bloqueio pode ser qualquer objeto e a classe de qualquer objeto herda a classe Object.
  3. O método wait e o método notify devem ser usados ​​em um bloco de código de sincronização ou em uma função de sincronização, e esses dois métodos devem ser chamados por meio do objeto de bloqueio.

Operação de bloqueio de liberação e bloqueio

1. A operação de liberação da trava

  • A execução do método de sincronização e do bloco de código de sincronização do thread atual termina.

  • Ocorre um erro ou exceção não tratada no bloco de código de sincronização ou no método de sincronização do thread atual, fazendo com que o thread atual termine de forma anormal.

  • O thread atual executa o método wait() do objeto de bloqueio no bloco de código de sincronização e no método de sincronização, o thread atual é suspenso e o bloqueio é liberado.

2. Impasse

Diferentes threads bloqueiam o objeto do monitor de sincronização exigido pela outra parte e não o liberam.Quando eles estão esperando que a outra parte desista primeiro, um impasse de thread é formado. Quando ocorre um impasse, todo o programa não será anormal nem fornecerá nenhum prompt, mas todos os encadeamentos serão bloqueados e não poderão continuar.

3. Pergunta da entrevista: a diferença entre os métodos sleep() e wait()

(1) sleep() não libera o bloqueio, wait() libera o bloqueio

(2) sleep() especifica o tempo de inatividade, wait() pode especificar o tempo ou esperar indefinidamente até notificar ou notificarTodos

(3) sleep() é um método estático declarado na classe Thread, e o método wait é declarado na classe Object

Porque chamamos o método wait () é chamado pelo objeto de bloqueio e o tipo do objeto de bloqueio é qualquer tipo de objeto. Então os métodos que você deseja que qualquer tipo de objeto tenha só podem ser declarados na classe Object.

prática

Duas threads são necessárias para imprimir letras ao mesmo tempo, e cada thread pode imprimir 3 letras continuamente. Dois threads imprimem alternadamente, um thread imprime a forma minúscula da letra e um thread imprime a forma maiúscula da letra, mas as letras são consecutivas. Depois que a letra fizer um loop para z, volte para a.

insira a descrição da imagem aqui

public class PrintLetterDemo {
    
    
	public static void main(String[] args) {
    
    
		// 2、创建资源对象
		PrintLetter p = new PrintLetter();

		// 3、创建两个线程打印
		new Thread("小写字母") {
    
    
			public void run() {
    
    
				while (true) {
    
    
					p.printLower();
					try {
    
    
						Thread.sleep(1000);// 控制节奏
					} catch (InterruptedException e) {
    
    
						e.printStackTrace();
					}
				}
			}
		}.start();

		new Thread("大写字母") {
    
    
			public void run() {
    
    
				while (true) {
    
    
					p.printUpper();
					try {
    
    
						Thread.sleep(1000);// 控制节奏
					} catch (InterruptedException e) {
    
    
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
}

// 1、定义资源类
class PrintLetter {
    
    
	private char letter = 'a';

	public synchronized void printLower() {
    
    
		for (int i = 1; i <= 3; i++) {
    
    
			System.out.println(Thread.currentThread().getName() + "->" + letter);
			letter++;
			if (letter > 'z') {
    
    
				letter = 'a';
			}
		}
		this.notify();
		try {
    
    
			this.wait();
		} catch (InterruptedException e) {
    
    
			e.printStackTrace();
		}
	}

	public synchronized void printUpper() {
    
    
		for (int i = 1; i <= 3; i++) {
    
    
			System.out.println(Thread.currentThread().getName() + "->" + (char) (letter - 32));
			letter++;
			if (letter > 'z') {
    
    
				letter = 'a';
			}
		}
		this.notify();
		try {
    
    
			this.wait();
		} catch (InterruptedException e) {
    
    
			e.printStackTrace();
		}
	}
} 

Acho que você gosta

Origin blog.csdn.net/qq_52370789/article/details/129367778
Recomendado
Clasificación