Se alguém pesquisar no Google por "diferença entre notify()
e notifyAll()
", muitas explicações aparecerão (deixando de lado os parágrafos do javadoc). Tudo se resume ao número de threads em espera sendo ativados: um dentro notify()
e tudo dentro notifyAll()
.
No entanto (se eu entender a diferença entre esses métodos corretamente), somente um encadeamento será sempre selecionado para aquisição posterior do monitor; no primeiro caso, o selecionado pela VM, no segundo caso, o selecionado pelo planejador de encadeamentos do sistema. Os procedimentos de seleção exata para os dois (no caso geral) não são conhecidos pelo programador.
Qual é a diferença útil entre notify () e notifyAll () então? Estou esquecendo de algo?
java
multithreading
Sergey Mikhanov
fonte
fonte
synchronized
, exceto por certas , casos de uso bastante raros.notifyAll()
caso, os outros threads após o primeiro permanecem ativos e adquirem o monitor, um por um. Nonotify
caso, nenhum dos outros threads é ativado. Então, funcionalmente, eles são muito diferentes!blocked
e nãowaiting
. Quandoblocked
seu exec é suspenso temporariamente até que outro thread esteja dentro dosync
bloco.Respostas:
Isso não está correto.
o.notifyAll()
ativa todos os threads bloqueados naso.wait()
chamadas. Os threads só podem retornaro.wait()
um a um, mas cada um terá a sua vez.Simplificando, isso depende do motivo pelo qual seus threads estão aguardando para serem notificados. Deseja dizer a um dos threads em espera que algo aconteceu ou deseja contar a todos ao mesmo tempo?
Em alguns casos, todos os threads em espera podem executar ações úteis quando a espera terminar. Um exemplo seria um conjunto de threads aguardando a conclusão de uma determinada tarefa; Após a conclusão da tarefa, todos os threads em espera podem continuar com seus negócios. Nesse caso, você usaria o notifyAll () para ativar todos os threads em espera ao mesmo tempo.
Outro caso, por exemplo, bloqueio mutuamente exclusivo, apenas um dos threads em espera pode fazer algo útil após ser notificado (nesse caso, adquira o bloqueio). Nesse caso, você prefere usar o notify () . Implementado adequadamente, você também pode usar notifyAll () nessa situação, mas acionaria desnecessariamente threads que não podem fazer nada.
Em muitos casos, o código para aguardar uma condição será gravado como um loop:
Dessa forma, se uma
o.notifyAll()
chamada ativar mais de um encadeamento em espera, e o primeiro a retornar daso.wait()
marcas deixar a condição no estado falso, os outros encadeamentos que foram despertados voltarão à espera.fonte
Claramente,
notify
ativa (qualquer) um segmento no conjunto de espera,notifyAll
ativa todos os segmentos no conjunto de espera. A discussão a seguir deve esclarecer qualquer dúvida.notifyAll
deve ser usado na maioria das vezes. Se você não tiver certeza de qual usar, usenotifyAll
. Consulte a explicação a seguir.Leia com muito cuidado e entenda. Por favor, envie-me um e-mail se você tiver alguma dúvida.
Observe o produtor / consumidor (suposição é uma classe ProducerConsumer com dois métodos). ESTÁ QUEBRADO (porque usa
notify
) - sim, PODE funcionar - mesmo na maioria das vezes, mas também pode causar um impasse - veremos o porquê:PRIMEIRAMENTE,
Por que precisamos de um loop while em torno da espera?
Precisamos de um
while
loop caso tenhamos essa situação:O consumidor 1 (C1) entra no bloco sincronizado e o buffer está vazio, então C1 é colocado no conjunto de espera (por meio da
wait
chamada). O consumidor 2 (C2) está prestes a entrar no método sincronizado (no ponto Y acima), mas o Produtor P1 coloca um objeto no buffer e, posteriormente, chamanotify
. O único encadeamento em espera é C1, portanto ele é ativado e agora tenta readquirir o bloqueio de objeto no ponto X (acima).Agora C1 e C2 estão tentando adquirir o bloqueio de sincronização. Um deles (não-deterministicamente) é escolhido e entra no método, o outro é bloqueado (sem esperar - mas bloqueado, tentando obter o bloqueio no método). Digamos que C2 consiga o bloqueio primeiro. C1 ainda está bloqueando (tentando adquirir o bloqueio em X). C2 conclui o método e libera o bloqueio. Agora, C1 adquire o bloqueio. Adivinhe, por sorte, temos um
while
loop, porque C1 executa a verificação de loop (guarda) e é impedido de remover um elemento inexistente do buffer (C2 já o conseguiu!). Se não tivéssemos umwhile
, teríamos umIndexArrayOutOfBoundsException
como C1 tenta remover o primeiro elemento do buffer!AGORA,
Ok, agora, por que precisamos notificar tudo?
No exemplo produtor / consumidor acima, parece que podemos nos safar
notify
. Parece assim, porque podemos provar que os guardas nos ciclos de espera para produtores e consumidores são mutuamente exclusivos. Ou seja, parece que não podemos ter um encadeamento aguardando oput
método, assim como oget
método, porque, para que isso seja verdade, o seguinte teria que ser verdadeiro:buf.size() == 0 AND buf.size() == MAX_SIZE
(suponha que MAX_SIZE não seja 0)No entanto, isso não é bom o suficiente, precisamos usar
notifyAll
. Vamos ver porque ...Suponha que tenhamos um buffer de tamanho 1 (para facilitar o exemplo). As etapas a seguir nos levam a um impasse. Observe que QUALQUER TEMPO em que um encadeamento é acordado com notificação, ele pode ser selecionado de maneira não determinística pela JVM - ou seja, qualquer encadeamento em espera pode ser despertado. Observe também que, quando vários threads estão bloqueando a entrada em um método (ou seja, tentando adquirir um bloqueio), a ordem de aquisição pode ser não determinística. Lembre-se também de que um encadeamento pode estar apenas em um dos métodos a qualquer momento - os métodos sincronizados permitem que apenas um encadeamento esteja executando (ou seja, mantendo o bloqueio de) qualquer método (sincronizado) na classe. Se ocorrer a seguinte sequência de eventos - resultados de conflito:
PASSO 1:
- P1 coloca 1 caractere no buffer
PASSO 2:
- Tentativas P2
put
- verifica o loop de espera - já é um caractere - aguardaPASSO 3:
- P3 tenta
put
- verifica o loop de espera - já é um caractere - aguardaPASSO 4:
- C1 tenta obter 1 caractere
- C2 tenta obter 1 caractere - blocos na entrada do
get
método- C3 tenta obter 1 caractere - blocos na entrada do
get
métodoETAPA 5:
- C1 está executando o
get
método - obtém o método char, chamanotify
, sai- O
notify
acorda P2- MAS, C2 entra no método antes que o P2 possa (o P2 deve readquirir o bloqueio), portanto, o P2 bloqueia a entrada no
put
método- C2 verifica o loop de espera, não há mais caracteres no buffer, então aguarda
- C3 entra no método após C2, mas antes de P2, verifica o loop de espera, não há mais caracteres no buffer, então aguarda
PASSO 6:
- AGORA: P3, C2 e C3 estão esperando!
- Finalmente P2 adquire o bloqueio, coloca um caractere no buffer, chama notifica, sai do método
PASSO 7:
- A notificação do P2 ativa o P3 (lembre-se de que qualquer thread pode ser ativado)
- O P3 verifica a condição do loop de espera, já existe um caractere no buffer, então aguarda.
- NÃO MAIS LINHAS PARA CHAMAR NOTIFICAR E TRÊS LINHAS SUSPENSAS PERMANENTEMENTE!
SOLUÇÃO: Substitua
notify
pornotifyAll
no código do produtor / consumidor (acima).fonte
notify
causas P3 (o thread selecionado neste exemplo) prosseguem a partir do ponto em que estavam aguardando (ou seja, dentro dowhile
loop). Existem outros exemplos que não causam conflito, no entanto, nesse caso, o uso denotify
não garante código livre de conflito. O uso denotifyAll
faz.Diferenças úteis:
Use notify () se todos os seus threads em espera forem intercambiáveis (a ordem em que eles despertam não importa) ou se você tiver apenas um thread em espera. Um exemplo comum é um pool de encadeamentos usado para executar trabalhos de uma fila - quando um trabalho é adicionado, um dos encadeamentos é notificado para ativar, executar o próximo trabalho e voltar a dormir.
Use notifyAll () para outros casos em que os threads em espera podem ter finalidades diferentes e devem poder ser executados simultaneamente. Um exemplo é uma operação de manutenção em um recurso compartilhado, em que vários encadeamentos aguardam a conclusão da operação antes de acessar o recurso.
fonte
Eu acho que depende de como os recursos são produzidos e consumidos. Se 5 objetos de trabalho estiverem disponíveis ao mesmo tempo e você tiver 5 objetos de consumidor, faria sentido ativar todos os encadeamentos usando notifyAll () para que cada um possa processar 1 objeto de trabalho.
Se você tiver apenas um objeto de trabalho disponível, qual é o sentido de ativar todos os objetos de consumidor para concorrer a esse objeto? A primeira verificação do trabalho disponível será obtida e todos os outros threads verificarão e descobrirão que eles não têm nada para fazer.
Encontrei uma ótima explicação aqui . Em resumo:
fonte
Observe que, com os utilitários de simultaneidade, você também pode escolher entre
signal()
esignalAll()
como esses métodos são chamados lá. Portanto, a questão permanece válida mesmo comjava.util.concurrent
.Doug Lea menciona um ponto interessante em seu famoso livro : se um
notify()
eThread.interrupt()
acontecer ao mesmo tempo, a notificação pode realmente se perder. Se isso puder acontecer e tiver implicações dramáticas,notifyAll()
é uma escolha mais segura, mesmo que você pague o preço da sobrecarga (ativando muitos threads na maioria das vezes).fonte
Pequeno resumo:
Sempre prefira notifyAll () em vez de notify (), a menos que você tenha um aplicativo massivamente paralelo em que um grande número de threads faça a mesma coisa.
Explicação:
fonte: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
Compare notify () com notifyAll () na situação descrita acima: um aplicativo massivamente paralelo em que os threads estão fazendo a mesma coisa. Se você chamar notifyAll () nesse caso, notifyAll () induzirá a ativação (ou seja, agendamento) de um grande número de threads, muitos deles desnecessariamente (já que apenas um thread pode realmente prosseguir, a saber, o thread ao qual será concedido o monitore o objeto wait () , notify () ou notifyAll () foi chamado), desperdiçando, portanto, recursos de computação.
Portanto, se você não tiver um aplicativo em que um grande número de threads faça a mesma coisa simultaneamente, prefira notifyAll () em vez de notify () . Por quê? Porque, como outros usuários já responderam neste fórum, notifique ()
origem: API Java SE8 ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )
Imagine que você tem um aplicativo de consumidor produtor onde os consumidores estão prontos (ou seja, aguardando ) para consumir, os produtores estão prontos (ou seja, aguardando ) para produzir e a fila de itens (a serem produzidos / consumidos) está vazia. Nesse caso, notify () pode acordar apenas consumidores e nunca produtores, porque a escolha de quem é acordado é arbitrária . O ciclo de consumo do produtor não faria nenhum progresso, embora produtores e consumidores estejam prontos para produzir e consumir, respectivamente. Em vez disso, um consumidor é acordado (ou seja, deixando o status de espera () ), não retira um item da fila porque está vazio e notifica () outro consumidor para prosseguir.
Por outro lado, notifyAll () desperta produtores e consumidores. A escolha de quem está agendado depende do agendador. Obviamente, dependendo da implementação do agendador, o agendador também pode agendar apenas consumidores (por exemplo, se você atribuir uma prioridade muito alta aos encadeamentos do consumidor). No entanto, a suposição aqui é que o perigo do planejador agendar apenas consumidores é menor do que o perigo da JVM despertar apenas os consumidores porque qualquer planejador razoavelmente implementado não toma apenas decisões arbitrárias . Em vez disso, a maioria das implementações do planejador faz pelo menos algum esforço para evitar a fome.
fonte
Aqui está um exemplo. Executá-lo. Em seguida, altere um dos notifyAll () para notify () e veja o que acontece.
Classe ProducerConsumerExample
Classe Dropbox
Classe de consumidor
Classe de produtores
fonte
De Joshua Bloch, o próprio Java Guru na edição Java Efetiva 2ª:
"Item 69: Prefira utilitários de concorrência para aguardar e notificar".
fonte
Espero que isso esclareça algumas dúvidas.
notify () : O método notify () ativa um thread aguardando o bloqueio (o primeiro thread que chamou wait () nesse bloqueio).
notifyAll () : O método notifyAll () ativa todos os threads que aguardam o bloqueio; a JVM seleciona um dos encadeamentos da lista de encadeamentos que aguardam o bloqueio e ativa esse encadeamento.
No caso de um único encadeamento aguardando um bloqueio, não há diferença significativa entre notify () e notifyAll (). No entanto, quando houver mais de um encadeamento aguardando pelo bloqueio, tanto no notify () quanto no notiteAll (), o encadeamento exato acordado estará sob o controle da JVM e você não poderá controlar programaticamente a ativação de um encadeamento específico.
À primeira vista, parece que é uma boa ideia ligar para notify () para ativar um thread; pode parecer desnecessário ativar todos os threads. No entanto, o problema com notify () é que o encadeamento acordado pode não ser o adequado para ser acordado (o encadeamento pode estar esperando por alguma outra condição ou a condição ainda não está satisfeita para esse encadeamento etc.). Nesse caso , o notify () pode ser perdido e nenhum outro encadeamento será ativado, levando a um tipo de conflito (a notificação é perdida e todos os outros encadeamentos aguardam a notificação - para sempre).
Para evitar esse problema , é sempre melhor chamar notifyAll () quando houver mais de um encadeamento aguardando um bloqueio (ou mais de uma condição na qual a espera é feita). O método notifyAll () ativa todos os threads, portanto, não é muito eficiente. no entanto, essa perda de desempenho é insignificante em aplicativos do mundo real.
fonte
Existem três estados para um encadeamento.
Agora, quando um notify () é chamado, a JVM escolhe um encadeamento e os move para o estado BLOCKED e, portanto, para o estado RUNNING, pois não há concorrência para o objeto de monitor.
Quando um notifyAll () é chamado, a JVM seleciona todos os encadeamentos e os move para o estado BLOCKED. Todos esses encadeamentos obterão o bloqueio do objeto com prioridade. O fio que é capaz de adquirir o monitor primeiro poderá ir primeiro ao estado RUNNING e assim por diante.
fonte
Estou muito surpreso que ninguém tenha mencionado o infame problema de "despertar perdido" (pesquise no google).
Basicamente:
ENTÃO, você deve usar o notifyAll, a menos que tenha garantias comprováveis de que desperdícios perdidos são impossíveis.
Um exemplo comum é uma fila FIFO simultânea em que: vários enfileiradores (1. e 3. acima) podem fazer a transição da fila de vazios para não enfraquecidos. Os enfileiradores múltiplos (2. acima) podem aguardar a condição "a fila não está vazia" vazia -> não vazio deve notificar dequeuers
Você pode escrever facilmente uma intercalação de operações na qual, a partir de uma fila vazia, 2 enfileiradores e 2 enfileiradores interagem e 1 enfileirador permanecerá adormecido.
Este é um problema sem dúvida comparável ao problema de deadlock.
fonte
notify()
vai acordar um thread enquantonotifyAll()
vai acordar tudo. Até onde eu sei, não há meio termo. Mas se você não tiver certeza do quenotify()
fará com seus threads, usenotifyAll()
. Funciona sempre como um encanto.fonte
Todas as respostas acima estão corretas, pelo que sei, então vou lhe contar outra coisa. Para o código de produção, você realmente deve usar as classes em java.util.concurrent. Há muito pouco que eles não podem fazer por você, na área de concorrência em java.
fonte
notify()
permite escrever código mais eficiente do quenotifyAll()
.Considere o seguinte pedaço de código que é executado a partir de vários threads paralelos:
Pode ser mais eficiente usando
notify()
:No caso, se você tiver um grande número de threads, ou se a condição do loop de espera for dispendiosa para avaliar,
notify()
será significativamente mais rápida quenotifyAll()
. Por exemplo, se você tiver 1000 threads, 999 threads serão despertados e avaliados após o primeironotifyAll()
, 998, 997 e assim por diante. Pelo contrário, com anotify()
solução, apenas um segmento será despertado.Use
notifyAll()
quando precisar escolher qual thread fará o trabalho a seguir:Finalmente, é importante entender que, no caso de
notifyAll()
, o código dentro dossynchronized
blocos que foram despertados será executado sequencialmente, não todos de uma vez. Digamos que há três threads aguardando no exemplo acima e o quarto thread chamanotifyAll()
. Todos os três encadeamentos serão despertados, mas apenas um iniciará a execução e verificará a condição dowhile
loop. Se a condição fortrue
, ela será chamadawait()
novamente e somente então o segundo encadeamento começará a executar e verificará suawhile
condição de loop, e assim por diante.fonte
Aqui está uma explicação mais simples:
Você está correto ao usar o notify () ou notifyAll (), o resultado imediato é que exatamente um outro encadeamento adquirirá o monitor e começará a executar. (Supondo que alguns encadeamentos foram de fato bloqueados em espera () por esse objeto, outros encadeamentos não relacionados não estão absorvendo todos os núcleos disponíveis etc.) O impacto ocorre mais tarde.
Suponha que os segmentos A, B e C estivessem aguardando esse objeto e o segmento A obtenha o monitor. A diferença está no que acontece quando A libera o monitor. Se você usou o notify (), B e C ainda estão bloqueados em wait (): eles não estão esperando no monitor, eles estão esperando para serem notificados. Quando A libera o monitor, B e C ainda estão sentados, aguardando uma notificação ().
Se você usou notifyAll (), B e C avançaram além do estado "aguardar notificação" e aguardam a aquisição do monitor. Quando A libera o monitor, B ou C o adquirirá (assumindo que nenhum outro encadeamento esteja competindo por esse monitor) e começará a executar.
fonte
Esta resposta é uma reescrita gráfica e simplificação da excelente resposta por xagyg , incluindo comentários de eran .
Por que usar o notifyAll, mesmo quando cada produto é destinado a um único consumidor?
Considere produtores e consumidores, simplificados da seguinte forma.
Produtor:
Consumidor:
Suponha que 2 produtores e 2 consumidores compartilhem um buffer de tamanho 1. A figura a seguir mostra um cenário que leva a um impasse , que seria evitado se todos os threads usados notificassem .
Cada notificação é rotulada com o segmento que está sendo ativado.
fonte
Gostaria de mencionar o que é explicado no Java Concurrency in Practice:
Primeiro ponto, seja o Notify ou o NotifyAll?
Esse problema pode ser resolvido com o uso do objeto Condition do bloqueio de bloqueio explícito, fornecido no jdk 5, pois fornece uma espera diferente para cada predicado de condição. Aqui ele se comportará corretamente e não haverá problema de desempenho, pois chamará o sinal e garantirá que apenas um encadeamento esteja aguardando essa condição
fonte
notify()
- Seleciona um encadeamento aleatório do conjunto de espera do objeto e o coloca noBLOCKED
estado. O restante dos encadeamentos no conjunto de espera do objeto ainda está noWAITING
estado.notifyAll()
- Move todos os threads do conjunto de espera do objeto para oBLOCKED
estado. Após o usonotifyAll()
, não há segmentos restantes no conjunto de espera do objeto compartilhado porque todos eles estão agora noBLOCKED
estado e não noWAITING
estado.BLOCKED
- bloqueado para aquisição de bloqueio.WAITING
- aguardando notificação (ou bloqueado para conclusão da associação).fonte
Retirado do blog sobre Java eficaz:
Então, o que eu entendo é (do blog mencionado anteriormente, comente por "Yann TM" sobre resposta aceita e documentos Java ):
fonte
Veja o código postado por @xagyg.
Suponha que dois encadeamentos diferentes aguardem duas condições diferentes:
O primeiro encadeamento está aguardando
buf.size() != MAX_SIZE
e o segundo encadeamento está aguardandobuf.size() != 0
.Suponha que em algum momento
buf.size()
não seja igual a 0 . Chamadas JVM emnotify()
vez denotifyAll()
e o primeiro encadeamento é notificado (não o segundo).O primeiro encadeamento é acordado, verifica o
buf.size()
que pode retornarMAX_SIZE
e volta à espera. O segundo segmento não é ativado, continua aguardando e não chamaget()
.fonte
notify()
acorda o primeiro segmento que chamouwait()
o mesmo objeto.notifyAll()
ativa todos os threads que chamaramwait()
o mesmo objeto.O thread de maior prioridade será executado primeiro.
fonte
notify()
não ser exatamente " o primeiro tópico ".notificar notificará apenas um encadeamento que está no estado de espera, enquanto notificar todos notificará todos os encadeamentos no estado de espera agora todos os encadeamentos notificados e todos os encadeamentos bloqueados são elegíveis para o bloqueio, dos quais apenas um receberá o bloqueio e todos os outros (incluindo aqueles que estão no estado de espera anteriormente) estarão no estado bloqueado.
fonte
Para resumir as excelentes explicações detalhadas acima, e da maneira mais simples que posso pensar, isso se deve às limitações do monitor interno da JVM, que 1) é adquirido em toda a unidade de sincronização (bloco ou objeto) e 2) não discrimina a condição específica que está sendo aguardada / notificada em / sobre.
Isso significa que, se vários encadeamentos estiverem aguardando em condições diferentes e a notificação () for usada, o encadeamento selecionado pode não ser aquele que faria progresso na condição recém-cumprida - causando esse encadeamento (e outros encadeamentos ainda aguardando que poderiam para cumprir a condição, etc.) não ser capaz de progredir e, eventualmente, morrer de fome ou interromper o programa.
Por outro lado, notifyAll () permite que todos os threads em espera adquiram novamente o bloqueio e verifiquem suas respectivas condições, permitindo, assim, o progresso.
Portanto, o notify () pode ser usado com segurança somente se for garantido qualquer thread em espera para permitir o progresso, caso seja selecionado, o que geralmente é satisfeito quando todos os threads no mesmo monitor verificam apenas uma e a mesma condição - uma situação bastante rara caso em aplicações do mundo real.
fonte
Quando você chama wait () do "objeto" (esperando que o bloqueio do objeto seja adquirido), internamente, isso liberará o bloqueio nesse objeto e ajudará os outros threads a bloquearem esse "objeto"; nesse cenário, haverá mais de um encadeamento aguardando o "recurso / objeto" (considerando que os outros encadeamentos também emitiram a espera no mesmo objeto acima e, no decorrer do caminho, haverá um encadeamento que preenche o recurso / objeto e chama o notify / notifyAll).
Aqui, quando você emitir a notificação do mesmo objeto (do mesmo / outro lado do processo / código), isso liberará um encadeamento único bloqueado e em espera (nem todos os encadeamentos em espera - esse encadeamento liberado será escolhido pelo JVM Thread O agendador e todo o processo de obtenção de bloqueios no objeto são iguais aos regulares).
Se você tiver apenas um segmento que estará compartilhando / trabalhando neste objeto, não há problema em usar o método notify () sozinho na sua implementação de espera-notificação.
se você estiver em uma situação em que mais de um encadeamento lê e grava recursos / objetos com base na sua lógica de negócios, você deve ir para notifyAll ()
Agora eu estou olhando exatamente como a jvm está identificando e quebrando o thread em espera quando emitimos o notify () em um objeto ...
fonte
Embora existam respostas sólidas acima, estou surpreso com o número de confusões e mal-entendidos que li. Provavelmente, isso prova a idéia de que se deve usar o java.util.concurrent, tanto quanto possível, em vez de tentar escrever seu próprio código simultâneo quebrado. De volta à pergunta: para resumir, a melhor prática hoje é EVITAR notify () em TODAS as situações devido ao problema de ativação perdida. Qualquer pessoa que não entenda isso não deve escrever código de simultaneidade de missão crítica. Se você estiver preocupado com o problema de agrupamento, uma maneira segura de ativar um encadeamento de cada vez é: 1. Construir uma fila de espera explícita para os encadeamentos em espera; 2. Faça com que cada um dos encadeamentos na fila aguarde seu antecessor; 3. Faça com que cada chamada de thread notifiqueAllAll () quando terminar. Ou você pode usar Java.util.concurrent. *,
fonte
Runnable
implementação) processa o conteúdo de uma fila. Owait()
é então usado sempre que a fila estiver vazia. E onotify()
é chamado quando as informações são adicionadas. -> nesse caso, há apenas 1 thread que chama owait()
, então não parece um pouco bobo usar anotifyAll()
se você souber que há apenas 1 thread em espera.Acordar tudo não faz muita importância aqui. wait notify e notifyall, todos estes são colocados depois de possuir o monitor do objeto. Se um segmento estiver no estágio de espera e a notificação for chamada, esse segmento ocupará o bloqueio e nenhum outro segmento nesse ponto poderá ocupar esse bloqueio. Portanto, o acesso simultâneo não pode ocorrer. Tanto quanto eu sei, qualquer chamada para aguardar, notificar e notificar só pode ser feita depois de bloquear o objeto. Corrija-me se eu estiver enganado.
fonte