Pergunta

Se alguém pesquisar no Google por "diferença entre notify() e notifyAll()" então 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 em notify() e tudo em notifyAll().

No entanto (se eu entendi corretamente a diferença entre esses métodos), apenas um thread é sempre selecionado para posterior aquisição do monitor;no primeiro caso aquele selecionado pela VM, no segundo caso aquele selecionado pelo agendador de threads do sistema.Os procedimentos exatos de seleção para ambos (no caso geral) não são conhecidos pelo programador.

Qual é o útil diferença entre notificar() e notificarTodos() então?Estou esquecendo de algo?

Foi útil?

Solução

No entanto (se eu entendi corretamente a diferença entre esses métodos), apenas um thread é sempre selecionado para posterior aquisição do monitor.

Isso não está correto. o.notifyAll() acorda todos dos threads que estão bloqueados em o.wait() chamadas.Os threads só podem retornar de o.wait() um por um, mas cada um deles vai chegar a sua vez.


Simplificando, depende do motivo pelo qual seus tópicos estão aguardando para serem notificados.Você deseja contar a um dos threads em espera que algo aconteceu ou deseja contar a todos eles ao mesmo tempo?

Em alguns casos, todos os threads em espera podem realizar ações úteis quando a espera terminar.Um exemplo seria um conjunto de threads aguardando a conclusão de uma determinada tarefa;assim que a tarefa for concluída, todos os threads em espera poderão continuar com seus negócios.Nesse caso você usaria notificarTodos() 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 (neste caso, adquirir o bloqueio).Nesse caso, você prefere usar notificar().Implementado corretamente, você poderia usar notificarTodos() nesta situação também, mas você despertaria desnecessariamente threads que não podem fazer nada de qualquer maneira.


Em muitos casos, o código para aguardar uma condição será escrito como um loop:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

Dessa forma, se um o.notifyAll() chamada desperta mais de um thread em espera e o primeiro a retornar do o.wait() faz deixar a condição no estado falso, então as demais threads que foram despertadas voltarão a esperar.

Outras dicas

Claramente, notify acorda (qualquer) um thread no conjunto de espera, notifyAll desperta todos os threads no conjunto de espera.A discussão a seguir deve esclarecer quaisquer dúvidas. notifyAll deve ser usado na maior parte do tempo.Se você não tiver certeza de qual usar, use notifyAll.Por favor, veja a explicação a seguir.

Leia com muita atenção e entenda.Por favor, envie-me um e-mail se tiver alguma dúvida.

Veja produtor/consumidor (a suposição é uma classe ProducerConsumer com dois métodos).ESTÁ QUEBRADO (porque usa notify) - sim, PODE funcionar - até na maioria das vezes, mas também pode causar impasse - veremos o porquê:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

PRIMEIRAMENTE,

Por que precisamos de um loop while envolvendo a espera?

precisamos de while loop caso tenhamos esta situação:

O consumidor 1 (C1) entra no bloco sincronizado e o buffer está vazio, então C1 é colocado no conjunto de espera (através do wait chamar).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, chama notify.O único thread em espera é C1, então ele é acordado e agora tenta readquirir o bloqueio do 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 (não esperando - mas bloqueado, tentando adquirir o bloqueio do método).Digamos que C2 consiga o bloqueio primeiro.C1 ainda está bloqueando (tentando adquirir o bloqueio em X).C2 completa o método e libera o bloqueio.Agora, C1 adquire o bloqueio.Adivinhe, sorte que temos um while loop, pois C1 realiza a verificação do loop (guarda) e é impedido de remover um elemento inexistente do buffer (C2 já conseguiu!).Se não tivéssemos um while, obteríamos um IndexArrayOutOfBoundsException enquanto C1 tenta remover o primeiro elemento do buffer!

AGORA,

Ok, agora por que precisamos notificarAll?

No exemplo do produtor/consumidor acima, parece que podemos nos safar notify.Parece que sim, porque podemos provar que os guardas do espere os loops para produtor e consumidor são mutuamente exclusivos.Ou seja, parece que não podemos ter um thread esperando no put método, bem como o get método, porque, para que isso seja verdade, então 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 por que ...

Suponha que temos um buffer de tamanho 1 (para facilitar o acompanhamento do exemplo).As etapas a seguir nos levam a um impasse.Observe que A QUALQUER MOMENTO um thread é ativado com notificação, ele pode ser selecionado de forma não determinística pela JVM - ou seja, qualquer thread em espera pode ser ativado.Observe também que quando vários threads estão bloqueando a entrada de 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 thread só pode estar em um dos métodos por vez - os métodos sincronizados permitem que apenas um thread esteja em execução (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 - espera

ETAPA 3:
- tentativas P3 put - verifica o loop de espera - já é um caractere - espera

PASSO 4:
- C1 tenta obter 1 caractere
- C2 tenta obter 1 char - bloqueia na entrada do get método
- C3 tenta obter 1 char - bloqueia na entrada do get método

PASSO 5:
- C1 está executando o get método - pega o char, chama notify, sai do método
- O notify acorda P2
- MAS, C2 entra no método antes de P2 (P2 deve readquirir o bloqueio), então P2 bloqueia na entrada no put método
- C2 verifica o loop de espera, não há mais caracteres no buffer, então espera
- C3 entra no método depois de C2, mas antes de P2, verifica o loop de espera, não há mais caracteres no buffer, então espera

PASSO 6:
- AGORA:há P3, C2 e C3 esperando!
- Finalmente P2 adquire o lock, coloca um char no buffer, chama notify, sai do método

PASSO 7:
- A notificação do P2 desperta o P3 (lembre-se que qualquer thread pode ser despertado)
- P3 verifica a condição do loop de espera, já existe um char no buffer, então espera.
- NÃO HÁ MAIS TÓPICOS PARA CHAMAR NOTIFY e TRÊS TÓPICOS PERMANENTEMENTE SUSPENSOS!

SOLUÇÃO:Substituir notify com notifyAll no código produtor/consumidor (acima).

Diferenças úteis:

  • Usar notificar() se todos os seus threads em espera forem intercambiáveis ​​(a ordem em que eles são ativados não importa) ou se você tiver apenas um thread em espera.Um exemplo comum é um pool de threads usado para executar trabalhos de uma fila – quando um trabalho é adicionado, um dos threads é notificado para acordar, executar o próximo trabalho e voltar a dormir.

  • Usar notificarTodos() 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, onde vários threads aguardam a conclusão da operação antes de acessar o recurso.

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 consumidores, faria sentido ativar todos os threads 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 consumo para correr por esse objeto?O primeiro que verificar o trabalho disponível irá obtê-lo e todos os outros threads irão verificar e descobrir que não têm nada para fazer.

Achei um ótima explicação aqui.Resumidamente:

O método notify () é geralmente usado para conjuntos de recursos, onde há um número arbitrário de "consumidores" ou "trabalhadores" que tomam recursos, mas quando um recurso é adicionado ao pool, apenas um dos consumidores ou trabalhadores em espera pode lidar com isso.O método notifyAll () é realmente usado na maioria dos outros casos.Estritamente, é necessário notificar os garçons de uma condição que possa permitir que vários garçons prosseguem.Mas isso geralmente é difícil de saber.Então, como regra geral, Se você não tem lógica específica para usar notify (), provavelmente deve usar o notifyAll (), porque muitas vezes é difícil saber exatamente quais tópicos estarão esperando em um objeto específico e por quê.

Observe que com utilitários de simultaneidade você também pode escolher entre signal() e signalAll() como esses métodos são chamados lá.Portanto, a questão permanece válida mesmo com java.util.concurrent.

Doug Lea traz à tona um ponto interessante em seu livro famoso:se um notify() e Thread.interrupt() acontecer ao mesmo tempo, a notificação pode realmente se perder.Se isso pode acontecer e tem implicações dramáticas notifyAll() é uma escolha mais segura, mesmo que você pague o preço da sobrecarga (despertando muitos threads na maioria das vezes).

De Joshua Bloch, o próprio Java Guru em Effective Java 2ª edição:

"Item 69:Prefira utilitários de simultaneidade para esperar e notificar".

Aqui está um exemplo.Executá-lo.Em seguida, altere um dos notifyAll() para notify() e veja o que acontece.

Classe ProducerConsumerExample

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Aula do Dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

Classe de consumidor

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

Classe de produtor

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

Pequeno resumo:

Prefira sempre notificarTodos() sobre notificar() a menos que você tenha um aplicativo massivamente paralelo onde um grande número de threads fazem a mesma coisa.

Explicação:

notificar() ...] acorda um único tópico.Porque notificar() não permite que você especifique o thread que é Acordado, ele é útil apenas em aplicações massivamente paralelas — que é, programas com um grande número de threads, todos fazendo tarefas semelhantes.Nesse aplicativo, você não se importa com qual thread será ativado.

fonte: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Comparar notificar() com notificarTodos() na situação descrita acima:um aplicativo massivamente paralelo onde threads estão fazendo a mesma coisa.Se você ligar notificarTodos() nesse caso, notificarTodos() irá induzir o despertar (ou seja,agendamento) de um grande número de threads, muitos deles desnecessariamente (já que apenas um thread pode realmente prosseguir, ou seja, o thread que receberá o monitor do objeto espere(), notificar(), ou notificarTodos() foi chamado), desperdiçando, portanto, recursos de computação.

Assim, se você não possui uma aplicação onde um grande número de threads fazem a mesma coisa simultaneamente, prefira notificarTodos() sobre notificar().Por que?Porque, como outros usuários já responderam neste fórum, notificar()

ativa um único thread que está aguardando no monitor deste objeto....] A escolha é arbitrário e ocorre a critério da implementação.

fonte:API Java SE8 (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Imagine que você tem um aplicativo produtor-consumidor onde os consumidores estão prontos (ou seja, espere() ing) para consumir, os produtores estão prontos (ou seja, espere() ing) para produzir e a fila de itens (para serem produzidos/consumidos) está vazia.Nesse caso, notificar() pode acordar apenas os consumidores e nunca os produtores porque a escolha de quem é acordado é arbitrário.O ciclo produtor-consumidor não progrediria embora produtores e consumidores estivessem prontos para produzir e consumir, respectivamente.Em vez disso, um consumidor é acordado (ou seja,Deixe o espere() status), não retira um item da fila porque está vazio e notificar() é outro consumidor para prosseguir.

Em contraste, notificarTodos() desperta produtores e consumidores.A escolha de quem está agendado depende do agendador.É claro que, dependendo da implementação do escalonador, o escalonador também poderá agendar apenas consumidores (por exemplo,se você atribuir aos threads do consumidor uma prioridade muito alta).No entanto, a suposição aqui é que o perigo do escalonador agendar apenas os consumidores é menor do que o perigo da JVM apenas acordar os consumidores porque qualquer escalonador razoavelmente implementado não faz apenas arbitrário decisões.Em vez disso, a maioria das implementações de escalonadores faz pelo menos algum esforço para evitar a falta de recursos.

Estou muito surpreso que ninguém tenha mencionado o infame problema de "despertar perdido" (pesquise no Google).

Basicamente:

  1. se você tiver vários threads esperando na mesma condição e,
  2. vários threads que podem fazer a transição do estado A para o estado B e,
  3. vários threads que podem fazer a transição do estado B para o estado A (geralmente os mesmos threads de 1.) e,
  4. a transição do estado A para B deve notificar os threads em 1.

ENTÃO você deve usar notifyAll, a menos que tenha garantias comprováveis ​​de que as ativações perdidas são impossíveis.

Um exemplo comum é uma fila FIFO simultânea onde:vários enfileiradores (1.e 3.acima) pode fazer a transição de sua fila de vazia para não vazia vários desenfiladores (2.acima) pode aguardar a condição "a fila não está vazia" vazio -> não vazio deve notificar os desenfiladores

Você pode facilmente escrever uma intercalação de operações nas quais, a partir de uma fila vazia, 2 enfileiradores e 2 desenfileiradores interagem e 1 enfileirador permanecerá inativo.

Este é um problema indiscutivelmente comparável ao problema do impasse.

Espero que isso esclareça algumas dúvidas.

notificar() :O método notify() ativa um thread aguardando para o bloqueio (o primeiro thread que chamou wait() nesse bloqueio).

notificarTodos() :O método notifyAll() desperta todos os threads aguardando o bloqueio;a JVM seleciona um dos threads da lista de threads aguardando o bloqueio e ativa; que encadeamento.

No caso de um único thread esperando por um bloqueio, não há diferença significativa entre notificar() e notificarAll().No entanto, quando há mais de um thread aguardando o bloqueio, tanto em notify() quanto em notifyAll(), o thread exato acordado é sob o controle da JVM e você não pode controlar programaticamente a ativação de um thread específico.

À primeira vista, parece que é uma boa ideia apenas chamar notify() para ativar um thread;pode parecer desnecessário ativar todos os threads.No entanto, o problema com notify() é que o thread acordado pode não ser o adequado para ser acordado (o thread pode estar aguardando alguma outra condição ou a condição ainda não foi atendida para esse thread, etc.). Nesse caso, o notify() pode ser perdido e nenhum outro thread será ativado, potencialmente levando a um tipo de deadlock (a notificação será perdida e todos os outros threads estarão aguardando a notificação – para sempre).

Para evitar esse problema, é sempre melhor chamar notifyAll() quando há mais de um thread 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, esta perda de desempenho é insignificante em aplicações do mundo real.

notify() irá acordar um tópico enquanto notifyAll() vai acordar tudo.Até onde eu sei não existe meio termo.Mas se você não tem certeza do que notify() fará com seus tópicos, use notifyAll().Funciona perfeitamente sempre.

Todas as respostas acima estão corretas, pelo que posso dizer, então vou lhe contar mais uma coisa.Para código de produção, você realmente deve usar as classes em java.util.concurrent.Há muito pouco que eles não possam fazer por você na área de simultaneidade em java.

Aqui está uma explicação mais simples:

Você está correto ao usar notify() ou notifyAll(), o resultado imediato é que exatamente um outro thread adquirirá o monitor e começará a ser executado.(Supondo que alguns threads foram de fato bloqueados em wait() para este objeto, outros threads não relacionados não estão absorvendo todos os núcleos disponíveis, etc.) O impacto vem mais tarde.

Suponha que os threads A, B e C estejam aguardando esse objeto e que o thread A obtenha o monitor.A diferença está no que acontece quando A libera o monitor.Se você usou notify(), então B e C ainda estarão bloqueados em wait():eles não estão esperando no monitor, estão esperando para serem notificados.Quando A liberar o monitor, B e C ainda estarão lá, aguardando uma notificação ().

Se você usou notifyAll(), então B e C avançaram além do estado "aguardar notificação" e estão aguardando para adquirir o monitor.Quando A libera o monitor, B ou C irá adquiri-lo (assumindo que nenhum outro thread esteja competindo por esse monitor) e começará a executar.

Existem três estados para um thread.

  1. WAIT - O thread não está usando nenhum ciclo de CPU
  2. BLOQUEADO - O thread está bloqueado ao tentar adquirir um monitor. Ele ainda pode estar usando os ciclos da CPU
  3. RUNNING - O thread está em execução.

Agora, quando um notify() é chamado, a JVM escolhe um thread e o move para o estado BLOCKED e, portanto, para o estado RUNNING, pois não há competição pelo objeto monitor.

Quando um notifyAll() é chamado, a JVM seleciona todos os threads e move todos eles para o estado BLOCKED.Todos esses threads obterão o bloqueio do objeto com prioridade. O thread que for capaz de adquirir o monitor primeiro poderá ir primeiro para o estado RUNNING e assim por diante.

Esta resposta é uma reescrita gráfica e simplificação da excelente resposta por xagyg, incluindo comentários de era.

Por que usar o notifyAll, mesmo quando cada produto se destina a um único consumidor?

Considere produtores e consumidores, simplificados da seguinte forma.

Produtor:

while (!empty) {
   wait() // on full
}
put()
notify()

Consumidor:

while (empty) {
   wait() // on empty
}
take()
notify()

Suponha que 2 produtores e 2 consumidores compartilhem um buffer de tamanho 1.A imagem a seguir retrata um cenário que leva a um impasse, o que seria evitado se todos os threads usassem notificar todos.

Cada notificação é rotulada com o thread que está sendo ativado.

deadlock due to notify

notify() permite escrever código mais eficiente do que notifyAll().

Considere o seguinte trecho de código executado a partir de vários threads paralelos:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

Pode ser tornado mais eficiente usando notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

No caso de você ter um grande número de threads ou se a condição do loop de espera for cara para avaliar, notify() será significativamente mais rápido do que notifyAll().Por exemplo, se você tiver 1.000 threads, 999 threads serão despertados e avaliados após o primeiro notifyAll(), depois 998, depois 997 e assim por diante.Pelo contrário, com o notify() solução, apenas um thread será despertado.

Usar notifyAll() quando você precisar escolher qual thread fará o próximo trabalho:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Finalmente, é importante entender que no caso de notifyAll(), o código dentro synchronized os blocos que foram despertados serão executados sequencialmente, não todos de uma vez.Digamos que há três threads aguardando no exemplo acima e o quarto thread chama notifyAll().Todos os três threads serão despertados, mas apenas um iniciará a execução e verificará a condição do while laço.Se a condição for true, ele chamará wait() novamente, e só então o segundo thread começará a ser executado e verificará seu while condição de loop e assim por diante.

Tirado de blog em Java Efetivo:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

Então, o que eu entendi é (do blog mencionado acima, comentário de "Yann TM" em resposta aceita e Java documentos):

  • notificar():A JVM desperta um dos encadeamentos em espera neste objeto.A seleção de threads é feita arbitrariamente e sem justiça.Assim, o mesmo tópico pode ser despertado repetidamente.Assim, o estado do sistema muda, mas nenhum progresso real é feito.Criando assim um livelock.
  • notificarTodos() :A JVM desperta todos os threads e então todos os threads correm pelo bloqueio deste objeto.Agora, o escalonador da CPU seleciona um thread que adquire bloqueio neste objeto.Este processo de seleção seria muito melhor que a seleção por JVM.Assim, garantindo vivacidade.

Dê uma olhada no código postado por @xagyg.

Suponha que dois threads diferentes estejam aguardando duas condições diferentes:
O primeiro tópico está esperando por buf.size() != MAX_SIZE, e a segundo tópico está esperando por buf.size() != 0.

Suponha que em algum momento buf.size() não é igual a 0.Chamadas JVM notify() em vez de notifyAll(), e o primeiro thread é notificado (não o segundo).

O primeiro thread é ativado, verifica buf.size() que pode retornar MAX_SIZE, e volta a esperar.O segundo thread não é acordado, continua esperando e não chama get().

notify() acorda o primeiro thread que chamou wait() no mesmo objeto.

notifyAll() acorda todos os threads que chamaram wait() no mesmo objeto.

O thread de maior prioridade será executado primeiro.

Gostaria de mencionar o que é explicado em Java Concurrency in Practice:

Primeiro ponto, seja Notify ou NotifyAll?

It will be NotifyAll, and reason is that it will save from signall hijacking.

Se dois threads A e B estiverem aguardando em predicados de condição diferentes da mesma fila de condição e notificação é chamado, então cabe JVM para qual thread a JVM notificará.

Agora, se notify foi feito para thread A e JVM notificou thread B, então thread B vai acordar e ver que esta notificação não é útil para vai esperar de novo.E o Thread A nunca vai ficar sabendo disso sinal perdido e alguém sequestrou sua notificação.

Então, chamar notifyAll resolverá esse problema, mas novamente terá impacto no desempenho, pois notificará todos os threads e todos os threads competir pelo mesmo bloqueio e envolverá mudança de contexto e, portanto, carregar na CPU.Mas devemos nos preocupar com o desempenho apenas se for comportando-se corretamente, se o comportamento em si não estiver correto, então desempenho não adianta.

Este problema pode ser resolvido usando o objeto Condition do bloqueio explícito Lock, fornecido no jdk 5, pois fornece esperas diferentes para cada predicado de condição.Aqui ele se comportará corretamente e não haverá problemas de desempenho, pois chamará o sinal e garantirá que apenas um thread esteja aguardando essa condição

notify notificará apenas um thread que está em estado de espera, enquanto notify all notificará todos os threads em estado de espera agora todos os threads notificados e todos os threads bloqueados são elegíveis para o bloqueio, dos quais apenas um obterá o bloqueio e todos os outros (incluindo aqueles que estavam em estado de espera anteriormente) estarão em estado bloqueado.

notify() - Seleciona um thread aleatório do conjunto de espera do objeto e o coloca no BLOCKED estado.O restante dos threads no conjunto de espera do objeto ainda está no WAITING estado.

notifyAll() - Move todos os threads do conjunto de espera do objeto para BLOCKED estado.Depois de usar notifyAll(), não há threads restantes no conjunto de espera do objeto compartilhado porque todos eles estão agora em BLOCKED estado e não em WAITING estado.

BLOCKED - bloqueado para aquisição de bloqueio.WAITING - aguardando notificação (ou bloqueado para conclusão da adesão).

Para resumir as excelentes explicações detalhadas acima, e da maneira mais simples que consigo imaginar, isso se deve às limitações do monitor integrado da JVM, que 1) é adquirido em toda a unidade de sincronização (bloco ou objeto) e 2) não discrimina sobre a condição específica que está sendo aguardada/notificada.

Isso significa que se vários threads estiverem aguardando em condições diferentes e notify() for usado, o thread selecionado pode não ser aquele que progrediria na condição recém-cumprida - fazendo com que esse thread (e outros threads atualmente ainda em espera que poderiam para cumprir a condição, etc.) não ser capaz de progredir e, eventualmente, passar fome ou interromper o programa.

Em contraste, notifyAll() permite que todos os threads em espera eventualmente readquiram o bloqueio e verifiquem suas respectivas condições, permitindo assim que o progresso seja feito.

Portanto, notify() pode ser usado com segurança apenas se qualquer thread em espera permitir o progresso caso seja selecionado, o que em geral é satisfeito quando todos os threads dentro do mesmo monitor verificam apenas uma e a mesma condição - um procedimento bastante raro caso em aplicações do mundo real.

Quando você chama o wait() do "objeto"(esperando que o bloqueio do objeto seja adquirido),intern isso irá liberar o bloqueio daquele objeto e ajudar os outros threads a terem bloqueio neste "objeto", neste cenário haverá mais de 1 thread aguardando o "recurso/objeto" (considerando que os outros threads também emitiram a espera no mesmo objeto acima e no caminho haverá um thread que preencherá o recurso/objeto e invocará notify/notifyAll).

Aqui, quando você emite a notificação do mesmo objeto (do mesmo/outro lado do processo/código), isso liberará um thread único bloqueado e em espera (nem todos os threads em espera - esse thread liberado será escolhido pelo JVM Thread O agendador e todo o processo de obtenção de bloqueio no objeto são iguais aos normais).

Se você tiver apenas um thread que estará compartilhando/trabalhando neste objeto, não há problema em usar o método notificar() sozinho em sua implementação de notificação de espera.

se você estiver em uma situação em que mais de um thread lê e grava em recursos/objetos com base em sua lógica de negócios, você deve usar notifyAll()

agora estou vendo como exatamente o jvm está identificando e quebrando o thread de espera quando emitimos notify() em um objeto ...

Embora existam algumas respostas sólidas acima, estou surpreso com o número de confusões e mal-entendidos que li.Isso provavelmente prova a ideia de que se deve usar 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 perda de ativação.Qualquer pessoa que não entenda isso não deve ter permissão para escrever código de simultaneidade de missão crítica.Se você está preocupado com o problema de pastoreio, uma maneira segura de despertar um thread de cada vez é:1.Construa uma fila de espera explícita para os threads em espera;2.Faça com que cada thread na fila aguarde seu antecessor;3.Faça com que cada thread chame notifyAll() quando terminar.Ou você pode usar Java.util.concurrent.*, que já implementou isso.

Acordar tudo não tem muito significado aqui.wait notify e notifyall, todos estes são colocados após possuir o monitor do objeto.Se um encadeamento estiver no estágio de espera e notificar for chamado, esse encadeamento ocupará o bloqueio e nenhum outro encadeamento nesse ponto poderá ocupá-lo.Portanto, o acesso simultâneo não pode ocorrer de forma alguma.Pelo que eu sei, qualquer chamada para wait notify e notifyall só pode ser feita após o bloqueio do objeto.Corrija-me se eu estiver enganado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top