O objetivo da comunicação de threads é permitir que os threads enviem sinais um para o outro. Por outro lado, a comunicação de threads permite que os threads aguardem sinais de outros threads.
Comunicação via objetos compartilhados
Uma maneira fácil de enviar sinais entre os threads é definir o valor do sinal nas variáveis do objeto compartilhado. Thread A Define a variável de membro booleano HasDatatoprocess como TRUE em um bloco de sincronização, e o Thread B também lê a variável HasDatatoprocess Member no bloco de sincronização. Este exemplo simples usa um objeto que segura um sinal e fornece os métodos de conjunto e verificação:
classe pública mysignal {protegido boolean hasdatatoprocess = false; public sincronizado boolean hasdatatoprocess () {return this.hasdatatoprocess; } public sincronizado void sethasdatatoprocess (boolean hasdata) {this.hasdatatoprocess = hasdata; }}Os threads A e B devem obter uma referência a uma instância compartilhada MySignal para comunicação. Se as referências que eles mantiverem apontar para diferentes instâncias mysingais, não serão capazes de detectar os sinais um do outro. Os dados a serem processados podem ser armazenados em uma área de cache compartilhada, que é armazenada separadamente da instância MySignal.
Espera ocupado
O thread B que está se preparando para processar os dados está aguardando os dados disponíveis. Em outras palavras, está aguardando um sinal do encadeamento A, que faz com que o hasdatatoprocess () retorne true. O thread B é executado em um loop para esperar por este sinal:
MySignal SharedSignal protegido = ...
espera (), notify () e notifyAll ()
A espera ocupada não utiliza efetivamente a CPU executando o segmento de espera, a menos que o tempo médio de espera seja muito curto. Caso contrário, é mais sábio tornar o fio de espera sono ou não correndo até receber o sinal que está esperando.
O Java possui um mecanismo de espera embutido para permitir que os threads se tornem sem correr enquanto aguardam sinais. A classe Java.lang.Object define três métodos, wait (), notify () e notifyAll (), para implementar esse mecanismo de espera.
Depois que um encadeamento chama o método wait () de qualquer objeto, ele se torna um estado não executado até que outro thread chamar o método notify () do mesmo objeto. Para ligar para Wait () ou notify (), o thread deve primeiro obter a trava desse objeto. Isto é, o thread deve chamar espera () ou notify () no bloco de sincronização. A seguir, é apresentada uma versão modificada de Mysingal - MywaitNotify usando wait () e notify ():
public class MonitorObject {} public class MywaitNotify {MonitorObject myMonitorObject = new MonitorObject (); public void dowait () {sincronizado (myMonitorObject) {try {myMonitorObject.wait (); } catch (interruptEdException e) {...}}} public void DonoTify () {Synchronized (myMonitorObject) {myMoMonitorObject.Notify (); }}}O thread em espera chamará dowait (), enquanto o tópico de despertar ligará para donotify (). Quando um thread chama o método notify () de um objeto, um dos threads que aguardam o objeto será despertado e permissão para executar (Nota: este thread a ser despertado é aleatório e não pode ser especificado qual encadeamento para acordar). Também é fornecido um método notifyAll () para acordar todos os threads aguardando um determinado objeto.
Como você pode ver, seja o thread de espera ou o thread de despertar, ele chama espera () e notify () no bloco de sincronização. Isso é obrigatório! Se um thread não segurar o bloqueio do objeto, ele não poderá chamar espera (), notify () ou notifyAll (). Caso contrário, uma exceção ilegalMonitorStateException será lançada.
(Nota: é assim que a JVM é implementada. Quando você liga para esperar, ele primeiro verifica se o tópico atual é o proprietário da fechadura e lança ilegalMonitorStateExcept.)
Mas como isso é possível? Ao aguardar a execução do thread no bloco de sincronização, não está sempre segurando a trava do objeto Monitor (objeto MyMonitor)? O fio de espera pode bloquear o encadeamento de despertar que entra no bloco síncrono do Donotify ()? A resposta é: é verdade. Depois que o thread chama o método wait (), ele libera o bloqueio no objeto Monitor Hold. Isso permitirá que outros threads ligue para Wait () ou Notify () também.
Depois que um thread é acordado, a chamada do método wait () não pode ser saiu imediatamente até que notify () seja chamado.
classe pública mywaitNotify2 {MonitorObject myMonitorObject = new MonitorObject (); boolean wasssignaled = false; public void dowait () {sincronizado (myMonitorObject) {if (! wassignaled) {try {myMonitorObject.wait (); } catch (interruptedException e) {...}} // Clear Signal e continue em execução. wasssignaled = false; }} public void donotify () {sincronizado (myMonitorObject) {WassSignaled = true; myMonitorObject.Notify (); }}}
O thread sai de seu próprio bloco de sincronização. Em outras palavras, o encadeamento acordado deve recuperar a trava do objeto Monitor antes que ele possa sair da chamada do método wait (), porque a chamada do método de espera é executada no bloco de sincronização. Se vários threads forem despertados pelo notifyAll (), ao mesmo tempo, apenas um thread poderá sair do método Wait (), porque cada encadeamento deve obter a trava do objeto Monitor antes de sair da espera ().
Sinais perdidos
Os métodos notify () e notifyAll () não salvam o método que os chama, porque quando esses dois métodos são chamados, é possível que nenhum encadeamento esteja no estado de espera. O sinal de notificação foi descartado. Portanto, se um encadeamento chama notify () antes de ser notificado antes de ligar para espera (), o tópico de espera perderá esse sinal. Isso pode ou não ser um problema. No entanto, em alguns casos, isso pode fazer com que o tópico de espera sempre espere e não acorde mais porque o thread perde o sinal de despertar.
Para evitar a perda de sinais, eles devem ser salvos na classe de sinal. No exemplo do MyWaitNotify, o sinal de notificação deve ser armazenado em uma variável de membro da instância do MyWaitNotify. Aqui está uma versão modificada do MywaitNotify:
classe pública mywaitNotify2 {MonitorObject myMonitorObject = new MonitorObject (); boolean wasssignaled = false; public void dowait () {sincronizado (myMonitorObject) {if (! wassignaled) {try {myMonitorObject.wait (); } catch (interruptedException e) {...}} // Clear Signal e continue em execução. wasssignaled = false; }} public void donotify () {sincronizado (myMonitorObject) {WassSignaled = true; myMonitorObject.Notify (); }}}Observe que o método doNotify () define a variável wassignalled como true antes de ligar para notificar (). Ao mesmo tempo, observe que o método dowait () verifica a variável WassSignaled antes de ligar para espera (). De fato, se nenhum sinal for recebido durante o período entre a chamada do Dowait () anterior e esta chamada do Dowait (), ele ligará apenas para Wait ().
(Nota de prova: para evitar a perda de sinal, use uma variável para salvar se ela foi notificada. Antes de notificar, defina -se para ter sido notificado. Depois de esperar, defina -se para não ter sido notificado e precisa esperar pela notificação.)
Fake Wake Up
Por alguma razão, é possível que o thread acorde sem ligar para notificar () e notifyAll (). Isso é chamado despertars espúrias. Acorde sem motivo.
Se ocorrer um despertar falso no método dowait () do mywaitnotify2, o encadeamento de espera poderá executar operações subsequentes, mesmo que não receba o sinal correto. Isso pode causar sérios problemas com seu aplicativo.
Para evitar o despertar falso, as variáveis do membro que mantêm o sinal serão verificadas em um loop de tempo, e não na expressão IF. Tanto o loop é chamado de bloqueio de spin (Nota: essa abordagem deve ser cautelosa. O spin atual da implementação da JVM consome a CPU. Se o método Donotify não for chamado por um longo tempo, o método do Dowait girará continuamente e a CPU consumirá muito). A rosca despertada girará até que a condição na trava de rotação (enquanto o loop) se torne falsa. A versão modificada a seguir do MyWaitNotify2 mostra o seguinte:
classe pública mywaitNotify3 {MonitorObject myMonitorObject = new MonitorObject (); boolean wasssignaled = false; public void dowait () {sincronizado (myMonitorObject) {while (! wassignaled) {try {myMonitorObject.wait (); } catch (interruptedException e) {...}} // Clear Signal e continue em execução. wasssignaled = false; }} public void donotify () {sincronizado (myMonitorObject) {WassSignaled = true; myMonitorObject.Notify (); }}}Observe que o método wait () está no while loop, não na expressão IF. Se o tópico de espera acordar sem receber o sinal, a variável WassSignaled se tornará falsa e o loop enquanto o loop será executado novamente, levando o tópico de despertar para retornar ao estado de espera.
Vários tópicos estão esperando o mesmo sinal
Se você tiver vários threads esperando e for despertado pelo notifyAll (), mas apenas um poderá continuar a execução, usando um loop enquanto também é uma boa maneira. Apenas um thread pode obter o bloqueio do objeto Monitor a cada vez, o que significa que apenas um thread pode sair da chamada Wait () e limpar o sinalizador WassSignaled (definido como false). Depois que este thread sair do bloco de sincronização dowait (), outros threads saem da ligue para Wait () e verifique o valor variável WassSignaled no loop while. No entanto, esse sinalizador foi limpo pelo primeiro tópico despertado; portanto, o restante dos threads despertados retornará ao estado de espera até a próxima vez que o sinal chegar.
Não ligue para esperar () em constantes de string ou objetos globais
(Prova Nota: a string constante mencionada neste capítulo refere -se a variáveis com valores constantes)
Uma versão anterior deste artigo usa constantes de string ("") como um objeto de tubulação no exemplo mywaitnotify. Aqui está o exemplo:
classe pública mywaitnotify {string myMonitorObject = ""; boolean wasssignaled = false; public void dowait () {sincronizado (myMonitorObject) {while (! wassignaled) {try {myMonitorObject.wait (); } catch (interruptedException e) {...}} // Clear Signal e continue em execução. wasssignaled = false; }} public void donotify () {sincronizado (myMonitorObject) {WassSignaled = true; myMonitorObject.Notify (); }}}O problema causado pela chamada wait () e notify () no bloco de sincronização de uma string vazia como um bloqueio (ou outra string constante) é que o JVM/compilador converterá a sequência constante no mesmo objeto. Isso significa que, mesmo se você tiver 2 instâncias diferentes do MyWaitNotify, todas elas se referem à mesma instância de string vazia. Isso também significa que existe o risco de que o thread chamando dowait () na primeira instância do MyWaitNotify seja despertado pelo thread chamando Donotify () na segunda instância do MyWaitNotify. Esta situação pode ser desenhada da seguinte forma:
No começo, isso pode não ser um grande problema. Afinal, se o Donotify () for chamado na segunda instância do MyWaitNotify, o que realmente acontece é que os threads A e B são despertados incorretamente. O encadeamento de despertar (a ou b) verificará o valor do sinal no while loop e depois retornará ao estado de espera, porque o Donotify () não é chamado na primeira instância do MyWaitNotify, e esta é a instância que está esperando. Essa situação é equivalente a desencadear um falso despertar. O encadeamento A ou B acorda sem o valor do sinal sendo atualizado. Mas o código lida com essa situação, então o tópico retorna ao estado de espera. Lembre -se, mesmo que 4 threads ligue para esperar () e notificar () na mesma instância de string compartilhada, os sinais em Dowait () e donotify () serão salvos por 2 instâncias MyWaitNotify, respectivamente. Uma chamada donotify () no mywaitnotify1 pode acordar o encadeamento do mywaitnotify2, mas o valor do sinal só será salvo no mywaitnotify1.
O problema é que, como o Donotify () chama apenas notify () em vez de notifyAll (), mesmo que haja 4 threads aguardando a mesma instância de string (String vazia), apenas um thread será despertado. Portanto, se o thread A ou B for despertado por um sinal enviado para C ou D, ele verificará seu próprio valor de sinal para ver se algum sinal é recebido e retornará ao estado de espera. Nem C nem D foram despertados para verificar o valor do sinal que eles realmente receberam, então o sinal foi perdido. Esta situação é equivalente ao problema de perder sinais mencionados acima. C e D foram enviados para o sinal, mas nenhum deles pode responder ao sinal.
Se o método donotify () chama notifyAll () em vez de notificar (), todos os threads de espera serão despertados e o valor do sinal será verificado por sua vez. Os threads A e B retornarão ao estado de espera, mas apenas um thread em C ou D percebe o sinal e sai da chamada do método Dowait (). O outro em C ou D retornará ao estado de espera porque o encadeamento que obteve o sinal limpa o valor do sinal (definido como false) durante o processo de saída do Dowait ().
Depois de ler o parágrafo acima, você pode tentar usar o notifyAll () em vez de notificar (), mas essa é uma má idéia baseada em desempenho. Quando apenas um thread pode responder ao sinal, não há razão para acordar todos os threads todas as vezes.
Então: no mecanismo wait ()/notify (), não use objetos globais, constantes de string, etc. O objeto exclusivo correspondente deve ser usado. Por exemplo, cada instância do mywaitNotify3 possui seu próprio objeto de monitor em vez de chamar espera ()/notify () em uma string vazia.
O exposto acima são as informações sobre a comunicação Java multi-threading e Thread. Continuaremos a adicionar informações relevantes no futuro. Obrigado pelo seu apoio a este site!