-
Como vários threads do mesmo processo compartilham o mesmo espaço de armazenamento, ao mesmo tempo que trazem comodidade, também traz o sério problema de conflitos de acesso. A linguagem Java fornece um mecanismo especial para resolver esse conflito, evitando efetivamente que o mesmo objeto de dados seja acessado por vários threads ao mesmo tempo.
Como podemos usar a palavra-chave private para garantir que o objeto de dados só possa ser acessado por métodos, precisamos apenas propor um mecanismo para métodos. Esse mecanismo é a palavra-chave sincronizada, que inclui dois usos: método sincronizado e bloco sincronizado.
1. Método sincronizado: declare o método sincronizado adicionando a palavra-chave sincronizada na declaração do método. como:
público sincronizado void accessVal(int newVal);
O método sincronizado controla o acesso às variáveis dos membros da classe: cada instância da classe corresponde a um bloqueio. Cada método sincronizado deve obter o bloqueio da instância da classe que chama o método antes de poder ser executado. Uma vez executado, o método ocupará exclusivamente a variável do membro da classe. O bloqueio não é liberado até retornar deste método. Depois disso, o thread bloqueado pode obter o bloqueio e entrar novamente no estado executável. Este mecanismo garante que para cada instância de classe ao mesmo tempo, no máximo uma de todas as suas funções membro declaradas como sincronizadas esteja no estado executável (porque no máximo pode-se obter o bloqueio correspondente à instância de classe) (preste atenção a esta instrução ! Porque é um bloqueio para uma instância de classe, portanto, para um objeto por vez, apenas um método sincronizado é executado por um thread por vez), evitando assim conflitos de acesso de variáveis de membros de classe (desde que todos os métodos que possam). variáveis de membro da classe de acesso são declaradas como sincronizadas).
Em Java, não apenas instâncias de classe, cada classe também corresponde a um bloqueio. Dessa forma, também podemos declarar as funções de membro estáticas da classe como sincronizadas para controlar seu acesso às variáveis de membro estáticas da classe.
Defeitos do método sincronizado: Se um método grande for declarado como sincronizado, a eficiência será bastante afetada. Normalmente, se o método da classe de thread run() for declarado como sincronizado, ele continuará a ser executado durante todo o ciclo de vida do thread. . Fará com que suas chamadas para quaisquer métodos sincronizados desta classe nunca sejam bem-sucedidas. Claro, podemos resolver esse problema colocando o código que acessa as variáveis dos membros da classe em um método especial, declarando-o como sincronizado e chamando-o no método principal, mas Java nos fornece uma solução melhor, que é o bloco sincronizado.
2. bloco sincronizado: declare o bloco sincronizado por meio da palavra-chave sincronizada. A sintaxe é a seguinte:
sincronizado(syncObject) {
//Código para permitir controle de acesso
}
Um bloco sincronizado é um bloco de código no qual o código deve obter o bloqueio do objeto syncObject (como mencionado anteriormente, pode ser uma instância de classe ou classe) antes de poder ser executado. Como pode atingir qualquer bloco de código e especificar o objeto bloqueado arbitrariamente, possui alta flexibilidade.
Bloqueio de thread (sincronização)
Para resolver conflitos de acesso a áreas de armazenamento compartilhado, Java introduziu um mecanismo de sincronização. Agora vamos examinar o acesso de recursos compartilhados por vários threads. Obviamente, o mecanismo de sincronização não é mais suficiente, porque os recursos necessários a qualquer momento podem não ser. pronto Para ser acessado, por sua vez, pode haver mais de um recurso pronto ao mesmo tempo. Para resolver o problema de controle de acesso neste caso, Java introduziu suporte para mecanismo de bloqueio.
Bloqueio refere-se a pausar a execução de um thread para aguardar a ocorrência de uma determinada condição (como a preparação de um recurso). Os alunos que estudaram sistemas operacionais devem estar familiarizados com ele. Java fornece um grande número de métodos para suportar o bloqueio. Vamos analisá-los um por um.
1. Método sleep(): sleep() permite que você especifique um período de tempo em milissegundos como parâmetro. Isso faz com que o thread entre no estado de bloqueio dentro do tempo especificado e não consiga obter o tempo de CPU. thread entra novamente no estado executável.
Normalmente, sleep() é usado ao esperar que um recurso esteja pronto: depois que o teste descobrir que a condição não foi atendida, deixe o thread bloquear por um período de tempo e depois teste novamente até que a condição seja atendida.
2. Métodos suspend() e resume() (fáceis de causar deadlock, desatualizados): Os dois métodos são usados juntos. suspend() faz com que o thread entre em um estado de bloqueio e não se recupere automaticamente. chamado, o thread pode entrar novamente no estado executável. Normalmente, suspend() e resume() são usados ao esperar por um resultado produzido por outro thread: depois que o teste descobre que o resultado não foi produzido, o thread é bloqueado e depois que outro thread produz o resultado, chama resume() para retomá-lo.
3. Método yield(): yield() faz com que o thread desista do tempo de CPU atualmente alocado, mas não faz com que o thread seja bloqueado, ou seja, o thread ainda está em um estado executável e pode receber tempo de CPU novamente em a qualquer momento. O efeito de chamar yield() é equivalente ao escalonador considerar que o thread executou tempo suficiente para passar para outro thread.
4. Métodos wait() e notify(): Os dois métodos são usados juntos. wait() faz com que o thread entre em um estado de bloqueio. Um deles permite especificar um período de tempo em milissegundos como parâmetro. outro não. Parâmetros, o primeiro entrará novamente no estado executável quando o notify() correspondente for chamado ou o tempo especificado for excedido, e o último deve ser chamado quando o notify() correspondente for chamado.
À primeira vista, eles não parecem ser diferentes dos pares de métodos suspend() e resume(), mas na verdade são completamente diferentes. A principal diferença é que todos os métodos descritos acima não irão liberar o bloqueio ocupado (se estiver ocupado) durante o bloqueio, enquanto esta regra oposta é o oposto.
As diferenças principais acima levam a uma série de diferenças detalhadas.
Em primeiro lugar, todos os métodos descritos acima pertencem à classe Thread, mas este par pertence diretamente à classe Object, ou seja, todos os objetos possuem este par de métodos. Isso pode parecer incrível a princípio, mas na verdade é muito natural, pois quando este par de métodos é bloqueado, o bloqueio ocupado deve ser liberado e o bloqueio é possuído por qualquer objeto. Chamar o método wait() de qualquer objeto causa o bloqueio. thread a ser bloqueado e o bloqueio no objeto é liberado. Chamar o método notify() de qualquer objeto fará com que um thread selecionado aleatoriamente seja bloqueado chamando o método wait() do objeto a ser desbloqueado (mas não será executado até que o bloqueio seja obtido).
Em segundo lugar, todos os métodos descritos acima podem ser chamados em qualquer local, mas este par de métodos (wait() e notify()) deve ser chamado em um método ou bloco sincronizado. O motivo também é muito simples, apenas em um método sincronizado. ou bloquear Somente o thread atual ocupa o bloqueio e o bloqueio pode ser liberado. Da mesma forma, o bloqueio no objeto que chama esse par de métodos deve pertencer ao thread atual, para que o bloqueio possa ser liberado. Portanto, o par de chamadas de método deve ser colocado em um método ou bloco sincronizado cujo objeto bloqueado seja o objeto que chama o par de métodos. Se esta condição não for atendida, embora o programa ainda possa compilar, ocorrerá uma exceção IllegalMonitorStateException em tempo de execução.
As características acima dos métodos wait() e notify() determinam que eles são frequentemente usados em conjunto com métodos ou blocos sincronizados. Compará-los com o mecanismo de comunicação entre processos do sistema operacional revelará suas semelhanças: métodos ou blocos sincronizados fornecem semelhantes. para as funções das primitivas do sistema operacional, sua execução não será interferida pelo mecanismo multi-threading, e esta contraparte é equivalente às primitivas de bloco e de ativação (este par de métodos são ambos declarados sincronizados). Sua combinação nos permite implementar uma série de algoritmos de comunicação entre processos requintados no sistema operacional (como algoritmos de semáforo) e pode ser usado para resolver vários problemas complexos de comunicação entre threads.
Dois pontos finais sobre os métodos wait() e notify():
Primeiro: o thread desbloqueado causado pela chamada do método notify() é selecionado aleatoriamente dos threads bloqueados pela chamada do método wait() do objeto. Não podemos prever qual thread será selecionado, portanto, tenha cuidado especial ao programar, para evitar. problemas decorrentes desta incerteza.
Segundo: além de notificar(), há também um método notificarAll() que também pode desempenhar uma função semelhante. A única diferença é que chamar o método notificarAll() removerá todos os threads bloqueados pela chamada do método wait() do. objeto de uma vez. Claro, apenas o thread que adquire o bloqueio pode entrar no estado executável.
Ao falar sobre bloqueio, temos que falar sobre impasse. Uma breve análise pode revelar que tanto o método suspend() quanto a chamada do método wait() sem especificar um período de tempo limite podem causar impasse. Infelizmente, Java não suporta a prevenção de impasses no nível da linguagem e devemos ter cuidado para evitar impasses na programação.
Acima, analisamos os vários métodos de bloqueio de threads em Java. Focamos nos métodos wait() e notify() porque eles são os mais poderosos e flexíveis de usar, mas isso também leva à sua eficiência. é mais sujeito a erros. No uso real, devemos usar vários métodos de forma flexível para melhor atingir nossos objetivos.
thread daemon
Threads daemon são um tipo especial de thread. A diferença entre eles e threads comuns é que eles não são a parte principal do aplicativo. Quando todos os threads não-daemon de um aplicativo terminam, mesmo que ainda haja threads daemon em execução, o aplicativo. will Terminate, por outro lado, o aplicativo não será encerrado enquanto houver um thread não-daemon em execução. Threads daemon geralmente são usados para fornecer serviços a outros threads em segundo plano.
Você pode determinar se um thread é um thread daemon chamando o método isDaemon() ou pode chamar o método setDaemon() para definir um thread como um thread daemon.
grupo de tópicos
Grupo de threads é um conceito exclusivo de Java, grupo de threads é um objeto da classe ThreadGroup. Cada thread pertence a um grupo de threads exclusivo. o tópico Mudar. Você pode especificar o grupo de encadeamentos ao qual o encadeamento pertence chamando o construtor da classe Thread que contém um parâmetro do tipo ThreadGroup. Se não for especificado, o encadeamento será padronizado para o grupo de encadeamentos do sistema denominado system.
Em Java, todos os grupos de encadeamentos devem ser criados explicitamente, exceto os grupos de encadeamentos do sistema pré-construídos. Em Java, cada grupo de threads, exceto o grupo de threads do sistema, pertence a outro grupo de threads. Você pode especificar o grupo de threads ao qual ele pertence ao criar um grupo de threads. Se não for especificado, ele pertence ao grupo de threads do sistema por padrão. Dessa forma, todos os grupos de encadeamentos formam uma árvore com o grupo de encadeamentos do sistema como raiz.
Java nos permite operar em todos os threads de um grupo de threads ao mesmo tempo. Por exemplo, podemos definir a prioridade de todos os threads nele chamando o método correspondente do grupo de threads, e também podemos iniciar ou bloquear todos os threads. isto.
Outra função importante do mecanismo de grupo de threads do Java é a segurança de threads. O mecanismo de grupo de threads nos permite distinguir threads com diferentes características de segurança por meio de agrupamento, tratar threads em diferentes grupos de maneira diferente e apoiar a adoção de medidas de segurança desiguais por meio da estrutura hierárquica de grupos de threads. A classe ThreadGroup do Java fornece um grande número de métodos para nos facilitar a operação de cada grupo de threads na árvore do grupo de threads e de cada thread no grupo de threads.
Estado do Thread Em um determinado momento, um thread só pode estar em um estado.
NOVO
Threads que ainda não foram iniciados estão neste estado.
EXECUTÁVEL
Threads em execução na máquina virtual Java estão neste estado.
BLOQUEADO
Um thread que está bloqueado e aguardando um bloqueio de monitor está neste estado.
ESPERANDO
Um thread que espera indefinidamente que outro thread execute uma operação específica está neste estado.
O status do thread de um thread em espera. Um thread está em estado de espera porque chamou um dos seguintes métodos:
Object.wait sem valor de tempo limite
Thread.join sem valor de tempo limite
LockSupport.parque
Um thread em estado de espera está aguardando que outro thread execute uma operação específica. Por exemplo, um thread que chamou Object.wait() em um objeto está aguardando que outro thread chame Object.notify() ou Object.notifyAll() nesse objeto. O thread que chamou Thread.join() está aguardando o término do thread especificado.
TIMED_WAITING
Um thread que está aguardando que outro thread execute uma operação que depende do tempo de espera especificado está neste estado.
O estado do thread de um thread em espera com um tempo de espera especificado. Um thread está em um estado de espera cronometrado chamando um dos seguintes métodos com um tempo de espera positivo especificado:
Thread.sleep
Object.wait com valor de tempo limite
Thread.join com valor de tempo limite
LockSupport.parkNanos
LockSupport.parkUntil
TERMINADO
Um thread encerrado está neste estado.