Esta série é baseada no curso de refinar os números em ouro e, para aprender melhor, uma série de registros foi feita. Este artigo apresenta principalmente 1. O que é um thread 2. Operações básicas dos threads 3. Daemon Thread 4. Prioridade do thread 5. Operações básicas de sincronização de threads
1. O que é tópico
Os threads são unidades de execução nos processos
Existem vários threads em um processo.
Um thread é uma unidade de execução dentro de um processo.
O motivo do uso de threads é que a troca de processos é uma operação muito pesada e consome recursos. Se você usar vários processos, o número de simultaneidade não será muito alto. Os threads são unidades de agendamento menores e são mais leves, portanto os threads são mais amplamente utilizados em design simultâneo.
Em Java, o conceito de threads é semelhante ao conceito de threads operacionais no nível do sistema. De fato, a JVM mapeará threads em Java para a área de roscas do sistema operacional.
2. Operações básicas de threads
2.1 Diagrama de status do thread
A figura acima mostra a operação básica dos threads em Java.
Quando um tópico é novo, o tópico realmente não funciona. Ele apenas gera uma entidade e o thread é realmente iniciado quando você chama o método de início desta instância. Após a startup, atinge o estado executável. Runnable significa que os recursos do thread e assim por diante foram preparados e podem ser executados, mas isso não significa que ele esteja no estado de execução. Devido à rotação de fatias de tempo, o encadeamento pode não estar executando no momento. Para nós, o encadeamento pode ser considerado executado, mas se é executado depende da programação da CPU física. Quando a tarefa do encadeamento é executada, o thread atinge o estado terminado.
Às vezes, durante a execução de um encadeamento, é inevitável que alguns bloqueios ou monitores de objetos sejam aplicados. Quando não pode ser recuperado, o fio será bloqueado, suspenso e atinge o estado bloqueado. Se este tópico chama o método de espera, está em um estado de espera. O encadeamento que entra no estado de espera aguardará outros threads para notificá -lo. Depois que a notificação for feita, o estado de espera mudará do estado de espera para o estado executável para continuar a execução. Obviamente, existem dois tipos de estados de espera, um deve esperar indefinidamente até que seja notificado. Ele estava esperando por um período limitado de tempo. Por exemplo, se você esperar 10 segundos ou não tiver sido notificado, ele alternará automaticamente para o estado executável.
2.2 Crie um novo tópico
Thread Thread = new Thread ();
thread.start ();
Isso abre um tópico.
Uma coisa a notar é
Thread Thread = new Thread ();
thread.run ();
Você não pode abrir um novo thread chamando o método de execução diretamente.
O método de início realmente chama o método de execução em um novo thread do sistema operacional. Em outras palavras, se você chamar o método de execução diretamente, em vez do método de início, ele não iniciará um novo thread, mas executará suas operações na execução atual de chamadas do thread.
Thread Thread = new Thread ("T1") {@Override public void run () {// TODO Method Auto-Generated Stub System.out.println (Thread.currentThread (). GetName ()); }}; thread.start (); Se o início for chamado, a saída será thread thread = new thread ("t1") {@Override public void run () {// TODO Auto-Gerated Method Stub System.out.println (thread.currentThread (). GetName ()); }}; thread.run (); Se for executado, a principal é a saída. (A execução diretamente é apenas uma chamada de função comum e não alcançou o papel da multi-threading)
Existem duas maneiras de implementar o método de execução
O primeiro método é substituir diretamente o método de execução. Como mostrado no código agora, a maneira mais conveniente pode ser alcançada usando uma classe anônima.
Thread Thread = new Thread ("T1") {@Override public void run () {// TODO Method Auto-Generated Stub System.out.println (Thread.currentThread (). GetName ()); }}; A segunda maneira
Thread t1 = novo thread (new createThread3 ());
CreateThread3 () implementa a interface executável.
No vídeo de Zhang Xiaoxiang, o segundo método é recomendado, dizendo que é mais orientado a objetos.
2.3 encerrar o thread
Thread.stop () não é recomendado. Libera todos os monitores
Foi claramente declarado no código -fonte que o método de parada está preguiçoso e o motivo também é explicado no Javadoc.
O motivo é que o método de parada é "violento". Não importa onde o thread seja executado, ele parará imediatamente de soltar o thread.
Quando o thread de gravação recebe o bloqueio, começa a escrever dados. Depois de escrever ID = 1, ele é interrompido e o bloqueio é liberado ao se preparar para definir o nome = 1. O thread de leitura obtém o bloqueio para a operação de leitura. A leitura do ID é 1 e o nome ainda é 0, o que causa inconsistência de dados.
O mais importante é que esse tipo de erro não lançará uma exceção e será difícil de detectar.
2.4 interrupção do thread
Existem 3 maneiras de interromper o tópico
public void thread.interrupt () // Thread de interrupção
public boolean thread.isinterrupted () // determinar se é interrompido
Public Static Boolean Thread.Interrupted () // Determine se é interrompido e limpe o status de interrupção atual
O que é interrupção do thread?
Se você não entende o mecanismo de interrupção do Java, é muito provável que essa explicação cause mal -entendida, e você acredita que chamar o método de interrupção do encadeamento definitivamente interrompe o thread.
De fato, a interrupção do Java é um mecanismo de colaboração. Em outras palavras, chamar o método de interrupção de um objeto Thread não interrompe necessariamente o encadeamento em execução, apenas requer que o encadeamento se interrompa no momento certo. Cada encadeamento possui um estado de interrupção booleano (não necessariamente a propriedade do objeto, de fato, esse estado não é realmente um campo de thread), e o método de interrupção simplesmente define o estado como true. Para threads não bloqueadores, o estado de interrupção é alterado, ou seja, thread.isinterrupted () retornará verdadeiro e não interromperá o programa;
public void run () {// Thread t1 while (true) {thread.yield (); }} t1.Interrup (); Isso não terá nenhum efeito para interromper o thread T1, mas o bit de status de interrupção é alterado.
Se você quiser encerrar este tópico com muito graciosamente, você deve fazer isso
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("interrompido!"); quebrar; } Thread.yield (); }}O uso de interrupções fornece certas garantias para a consistência dos dados.
Para threads em estados de bloqueio cancelável, como threads aguardando essas funções, thread.sleep (), object.wait () e thread.join (), este thread lançará uma interrupção depois de receber o sinal de interrupção e definirá o estado de interrupção de volta como false.
Para tópicos em estados desbloqueados, você pode escrever código como este:
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("interrompido!"); quebrar; } tente {thread.sleep (2000); } catch (interruptedException e) {System.out.println ("Interrutado quando o sono"); // Defina o status de interrupção. O bit de sinalizador de interrupção será limpo após o lançamento de um thread de exceção.CurrentThread (). Interrup (); } Thread.yield (); }}2.5 A linha do tópico está pendurada
Suspender e retomar os threads
suspense () não liberará o bloqueio
Se o bloqueio ocorrer antes do currículo (), ambos os métodos de ocorrência de impasse são métodos depreciados e não serão recomendados.
O motivo é que o suspensão não libera o bloqueio; portanto, nenhum encadeamento pode acessar o recurso de área crítica que é bloqueada por ele até que seja retomada por outros threads. Como a sequência de threads em execução não pode ser controlada, se o método de currículo de outros threads for executado primeiro, o suspenso mais tarde sempre ocupará a fechadura, fazendo com que um impasse ocorra.
Use o seguinte código para simular este cenário
teste de pacote; public class Test {objeto estático u = new Object (); testsussuspendThread t1 = novo testsuspendThread ("t1"); testsussuspendThread T2 = novo testsuspendThread ("t2"); classe estática public static testsUsEndThread estende o thread {public testSUSSPENDTHREAD (nome da string) {setName (nome); } @Override public void run () {Synchronized (u) {System.out.println ("em" + getName ()); Thread.CurrentThread (). Susten (); }} public static void main (string [] args) lança interruptedException {t1.start (); Thread.sleep (100); t2.start (); t1.resume (); T2.Resume (); t1.Join (); t2.Join (); }} Deixe T1 e T2 competir por um bloqueio ao mesmo tempo, o thread que está competido é suspenso e depois retomar. Logicamente falando, um thread deve ser liberado por currículo após competir por ele e, em seguida, outro thread compete pelo bloqueio e retoma.
A saída do resultado é:
em t1
em T2
Isso significa que ambos os threads competem pela trava, mas a luz vermelha no console ainda está acesa, o que significa que deve haver threads que não foram executados em T1 e T2. Vamos despejar e dar uma olhada
Verificou -se que o T2 foi suspenso. Isso cria um impasse.
2.6 Junte -se e Yeild
Yeild é um método estático nativo. Esse método é para liberar o tempo da CPU que possui e, em seguida, competir com outros threads (observe que os threads de Yeild ainda podem competir pela CPU, prestar atenção à diferença do sono). Também é explicado em Javadoc que Yeild é um método que basicamente não é usado e geralmente é usado em depuração e teste.
O método de junção significa aguardar que outros threads terminem, assim como o código na seção Suspender, você deseja que o thread principal aguarde que T1 e T2 terminem antes de terminar. Se não terminar, o encadeamento principal será bloqueado lá.
teste de pacote; public class Test {public volatile static int i = 0; classe estática pública addThread estende thread {@Override public void run () {for (i = 0; i <10000000; i ++); }} public static void main (string [] args) lança interruptedException {addThread em = new addThread (); at.start (); at.join (); System.out.println (i); }} Se o AT.JOIN do código acima for removido, o encadeamento principal será executado diretamente e o valor de I será muito pequeno. Se houver uma junção, o valor da impressão eu deverá ser 10000000.
Então, como o ingresso é implementado?
A natureza de se juntar
enquanto (isalive ())
{
espere (0);
}
O método junção () também pode passar um horário, o que significa esperar por um período limitado de tempo e acordar automaticamente após esse período.
Há uma pergunta como esta: quem notificará o tópico? Não há lugar para chamar de notificar na aula de threads?
Em Javadoc, foi encontrada uma explicação relevante. Quando um thread é concluído e encerrado, o método NotifyAll será chamado para acordar todos os threads aguardando a instância atual do encadeamento. Esta operação é feita pela própria JVM.
Portanto, o Javadoc também nos deu uma sugestão para não usar o Wait e Notify/NotifyAll nas instâncias do thread. Como a JVM se chamará, pode ser diferente do resultado esperado da sua chamada.
3. Tópico de daemon
A conclusão silenciosa de alguns serviços sistemáticos em segundo plano, como threads de coleta de lixo e threads de jit, pode ser entendida como threads daemon.
Quando todos os processos não daemon terminarem em um aplicativo Java, a máquina virtual Java sairá naturalmente.
Escrevi um artigo sobre como implementá -lo no Python, confira aqui.
É relativamente simples se tornar um daemon em Java.
Thread t = new Daemont ();
t.SetDaemon (true);
t.start ();
Isso abre um fio daemon.
teste de pacote; public class Test {public static class DaemonThread estende o thread {@Override public void run () {for (int i = 0; i <10000000; i ++) {System.out.println ("Hi"); }}} public static void main (string [] args) lança interruptedException {DaemonThread dt = new DaemonThread (); dt.start (); }} Quando o Thread DT não é um fio daemon, depois de correr, podemos ver a saída do console oi
Ao ingressar antes do início
dt.setDaemon (true);
O console sai diretamente e não possui saída.
4. Prioridade do thread
Existem 3 variáveis na classe Thread que definem a prioridade do encadeamento.
public final static int min_priority = 1; public final static int norma_priority = 5; public final static int max_priority = 10; teste de pacote; public class Test {public static class High Extends Thread {static int count = 0; @Override public void run () {while (true) {synchronized (test.class) {count ++; if (contagem> 10000000) {System.out.println ("High"); quebrar; }}}}} classe estática pública LOW estende thread {static int count = 0; @Override public void run () {while (true) {synchronized (test.class) {count ++; if (contagem> 10000000) {System.out.println ("Low"); quebrar; }}}}} public static void main (string [] args) lança interruptedException {alta alta = new high (); Baixo baixo = novo baixo (); high.setPriority (Thread.max_priority); low.setPriority (Thread.min_priority); low.start (); high.start (); }} Deixe um thread de alta prioridade e um encadeamento de baixa prioridade competir para uma trava ao mesmo tempo e veja qual é o que completa primeiro.
Obviamente, ele não precisa necessariamente ser concluído primeiro com alta prioridade. Depois de executá -lo várias vezes, descobri que a probabilidade de conclusão de alta prioridade é relativamente alta, mas ainda é possível concluí -lo primeiro com baixa prioridade.
5. Operação básica de sincronização de roscas
sincronizado e object.wait () obejct.notify ()
Para detalhes desta seção, consulte um blog que escrevi antes.
A principal coisa a notar é
Existem três maneiras de bloquear sincronizar:
Especifique o objeto bloqueado: bloqueie o objeto fornecido e obtenha o bloqueio do objeto fornecido antes de inserir o código de sincronização.
Atuando diretamente no método da instância: é equivalente a bloquear a instância atual. Antes de inserir o código de sincronização, você deve obter o bloqueio da instância atual.
Atuando diretamente sobre métodos estáticos: é equivalente a travar a classe atual. Antes de inserir o código síncrono, você deve obter o bloqueio da classe atual.
Se funcionar no método da instância, não novas duas instâncias diferentes
Ele funciona em métodos estáticos, desde que a classe seja a mesma, porque o bloqueio adicionado é uma classe.class e duas instâncias diferentes podem ser novas.
Como usar o Wait and Notify:
Use qualquer bloqueio, ligue para esperar e notificar
Não vou entrar em detalhes neste artigo.