A wait()
só faz sentido quando há também uma notify()
, por isso é sempre sobre a comunicação entre os threads, e isso precisa de sincronização para funcionar corretamente. Alguém poderia argumentar que isso deveria estar implícito, mas isso não ajudaria realmente, pelo seguinte motivo:
Semanticamente, você nunca apenas wait()
. Você precisa de alguma condição para ser saturado e, se não estiver, aguarde até que esteja. Então, o que você realmente faz é
if(!condition){
wait();
}
Mas a condição está sendo definida por um encadeamento separado, portanto, para que este funcione corretamente, você precisa de sincronização.
Mais algumas coisas erradas, onde apenas porque seu thread parou de esperar não significa que a condição que você está procurando é verdadeira:
Você pode obter despertares espúrios (o que significa que um segmento pode acordar da espera sem nunca ter recebido uma notificação) ou
A condição pode ser definida, mas um terceiro encadeamento torna a condição falsa novamente quando o encadeamento em espera é ativado (e recupera o monitor).
Para lidar com esses casos, o que você realmente precisa é sempre de uma variação disso:
synchronized(lock){
while(!condition){
lock.wait();
}
}
Melhor ainda, não mexa nas primitivas de sincronização e trabalhe com as abstrações oferecidas nos java.util.concurrent
pacotes.
Thread.interrupted()
.Vamos ilustrar quais problemas encontraríamos se
wait()
pudessem ser chamados fora de um bloco sincronizado com um exemplo concreto .Suponhamos que implementássemos uma fila de bloqueio (eu sei, já existe uma na API :)
Uma primeira tentativa (sem sincronização) pode parecer algo ao longo das linhas abaixo
Isto é o que potencialmente poderia acontecer:
Um segmento de consumidor chama
take()
e vê que obuffer.isEmpty()
.Antes que o encadeamento do consumidor continue a chamar
wait()
, um encadeamento produtor aparece e chama um conjunto completogive()
, ou seja,buffer.add(data); notify();
O segmento do consumidor agora chamará
wait()
(e perderá onotify()
que acabou de ser chamado).Se tiver azar, o encadeamento do produtor não produzirá mais
give()
como resultado do encadeamento do consumidor nunca ser ativado e teremos um bloqueio.Depois de entender o problema, a solução é óbvia: use
synchronized
para garantir quenotify
nunca seja chamado entreisEmpty
ewait
.Sem entrar em detalhes: esse problema de sincronização é universal. Como Michael Borgwardt aponta, esperar / notificar tem tudo a ver com comunicação entre threads, portanto você sempre terá uma condição de corrida semelhante à descrita acima. É por isso que a regra "esperar apenas dentro da sincronização" é imposta.
Um parágrafo do link postado por @Willie resume muito bem:
O predicado com o qual o produtor e o consumidor precisam concordar está no exemplo acima
buffer.isEmpty()
. E o contrato é resolvido garantindo que a espera e a notificação sejam executadas emsynchronized
blocos.Esta postagem foi reescrita como um artigo aqui: Java: Por que a espera deve ser chamada em um bloco sincronizado
fonte
return buffer.remove();
enquanto bloco mas depoiswait();
, funciona?wait
retornos.Thread.currentThread().wait();
namain
função cercada por try-catch forInterruptedException
. Semsynchronized
bloco, isso me dá a mesma exceçãoIllegalMonitorStateException
. O que faz chegar agora ao estado ilegal? Mas funciona dentro de umsynchronized
bloco.@Rollerball está certo. O
wait()
é chamado, para que o encadeamento possa aguardar a ocorrência de alguma condição quando essawait()
chamada ocorrer, o encadeamento é forçado a desistir de seu bloqueio.Para desistir de algo, você precisa possuí-lo primeiro. O thread precisa possuir o bloqueio primeiro. Daí a necessidade de chamá-lo dentro de um
synchronized
método / bloco.Sim, concordo com todas as respostas acima em relação a possíveis danos / inconsistências se você não verificou a condição no
synchronized
método / bloco. No entanto, como @ shrini1000 apontou, apenas chamarwait()
dentro de um bloco sincronizado não impedirá que essa inconsistência aconteça.Aqui está uma boa leitura ..
fonte
O problema que pode causar se você não sincronizar antes
wait()
é o seguinte:makeChangeOnX()
e verificar a condição while, e estivertrue
(x.metCondition()
retornafalse
, significa quex.condition
éfalse
), ele entrará nele. Logo antes dowait()
método, outro thread vai parasetConditionToTrue()
e define ox.condition
paratrue
enotifyAll()
.wait()
método (não afetado pelonotifyAll()
que aconteceu alguns momentos antes). Nesse caso, o 1º thread permanecerá aguardando a execução de outro threadsetConditionToTrue()
, mas isso pode não acontecer novamente.fonte
Todos sabemos que os métodos wait (), notify () e notifyAll () são usados para comunicações entre threads. Para se livrar do sinal perdido e problemas espúrios de ativação, o thread em espera sempre espera em algumas condições. por exemplo-
Em seguida, notificar a variável wasNotified dos conjuntos de encadeamentos para true e notificar.
Cada encadeamento possui seu cache local, para que todas as alterações sejam escritas primeiro e depois promovidas para a memória principal gradualmente.
Se esses métodos não fossem invocados no bloco sincronizado, a variável wasNotified não seria liberada na memória principal e estaria presente no cache local do encadeamento, de modo que o encadeamento em espera continuará aguardando o sinal, embora tenha sido redefinido pela notificação do encadeamento.
Para corrigir esses tipos de problemas, esses métodos são sempre chamados dentro do bloco sincronizado, o que garante que, quando o bloco sincronizado for iniciado, tudo será lido na memória principal e liberado na memória principal antes de sair do bloco sincronizado.
Obrigado, espero que esclareça.
fonte
Isso basicamente tem a ver com a arquitetura do hardware (ou seja, RAM e caches ).
Se você não usar
synchronized
junto comwait()
ounotify()
, outro encadeamento poderá inserir o mesmo bloco em vez de aguardar a entrada do monitor. Além disso, ao acessar, por exemplo, um array sem um bloco sincronizado, outro thread pode não ver as alterações nele ... na verdade, outro thread não verá nenhuma alteração quando já tiver uma cópia do array no cache de nível x ( aka caches de 1º / 2º / 3º nível) do segmento que processa o núcleo da CPU.Mas os blocos sincronizados são apenas um lado da medalha: se você realmente acessar um objeto dentro de um contexto sincronizado a partir de um contexto não sincronizado, o objeto ainda não será sincronizado, mesmo dentro de um bloco sincronizado, porque ele possui uma cópia do objeto em seu cache. Eu escrevi sobre esses problemas aqui: https://stackoverflow.com/a/21462631 e Quando um bloqueio contém um objeto não final, a referência do objeto ainda pode ser alterada por outro thread?
Além disso, estou convencido de que os caches de nível x são responsáveis pela maioria dos erros de tempo de execução não reproduzíveis. Isso ocorre porque os desenvolvedores geralmente não aprendem coisas de baixo nível, como o funcionamento da CPU ou como a hierarquia de memória afeta a execução de aplicativos: http://en.wikipedia.org/wiki/Memory_hierarchy
Continua sendo um enigma por que as classes de programação não começam primeiro com a hierarquia de memória e a arquitetura da CPU. "Olá mundo" não vai ajudar aqui. ;)
fonte
diretamente deste tutorial do java oracle:
fonte
Quando você chama notify () de um objeto t, o java notifica um método t.wait () específico. Mas, como o java pesquisa e notifica um método de espera específico.
java apenas analisa o bloco de código sincronizado que foi bloqueado pelo objeto t. java não pode pesquisar o código inteiro para notificar um t.wait () específico.
fonte
conforme documentos:
wait()
O método simplesmente significa que libera a trava no objeto. Portanto, o objeto será bloqueado apenas dentro do bloco / método sincronizado. Se o encadeamento estiver fora do bloco de sincronização significa que não está bloqueado, se não estiver bloqueado, o que você lançaria no objeto?fonte
Espera de encadeamento no objeto de monitoramento (objeto usado pelo bloco de sincronização). Pode haver n número de objetos de monitoramento em toda a jornada de um único encadeamento. Se o Thread aguardar fora do bloco de sincronização, não haverá nenhum objeto de monitoramento e também outro thread notificará para acessar o objeto de monitoramento, como o thread fora do bloco de sincronização saberia que foi notificado. Esse também é um dos motivos pelos quais wait (), notify () e notifyAll () estão na classe de objeto em vez da classe de encadeamento.
Basicamente, o objeto de monitoramento é um recurso comum aqui para todos os encadeamentos, e os objetos de monitoramento podem estar disponíveis apenas no bloco de sincronização.
fonte