work - Java: notify() vs. notifyAll() tudo de novo




threads in java tutorialspoint (18)

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

Isso não está correto. o.notifyAll() todos os threads bloqueados em chamadas o.wait() . Os tópicos só podem retornar de o.wait() um por um, mas cada um terá sua vez.

Simplificando, depende de por que seus tópicos estão aguardando para serem notificados. Você quer dizer a um dos tópicos em espera que algo aconteceu, ou você quer contar todos eles ao mesmo tempo?

Em alguns casos, todos os encadeamentos 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; uma vez que a tarefa tenha terminado, todos os threads de espera podem continuar com seus negócios. Nesse caso, você usaria notifyAll () para ativar todos os threads de 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ê preferiria usar notify () . Corretamente implementado, você poderia usar notifyAll () nesta situação também, mas você iria desperdiçar desnecessariamente threads que não podem fazer nada de qualquer maneira.

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

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

Dessa forma, se uma chamada o.notifyAll() mais de um encadeamento de espera, e o primeiro a retornar do o.wait() fizer com que a condição fique no estado false, os outros encadeamentos que foram despertados retornarão para esperar.

Se um Googles 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 acordados: um em notify() e all em notifyAll() .

No entanto (se eu realmente entender a diferença entre esses métodos), apenas um thread é sempre selecionado para aquisição adicional do monitor; no primeiro caso, o selecionado pela VM, no segundo caso, o 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 é a diferença útil entre notify() e notifyAll() então? Estou esquecendo de algo?


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();
    }
}

Classe 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 do 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 produtores

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) { }
        }
    }
}

Claramente, notify wake (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 maioria das vezes. Se você não tiver certeza de qual usar, use notifyAll . 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 (a suposição é uma classe ProducerConsumer com dois métodos). É QUEBRADO (porque usa notify ) - sim, pode funcionar - mesmo na maioria das vezes, mas também pode causar um 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 ao redor da espera?

Precisamos de um loop while no caso de termos essa situação:

Consumidor 1 (C1) entra no bloco sincronizado e o buffer está vazio, então C1 é colocado no conjunto de espera (através da chamada de wait ). O consumidor 2 (C2) está prestes a inserir o método sincronizado (no ponto Y acima), mas o Produtor P1 coloca um objeto no buffer e, subsequentemente, as chamadas notify . 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 (não espera - mas bloqueado, tentando adquirir o bloqueio no método). Digamos que o C2 consiga o bloqueio primeiro. C1 ainda está bloqueando (tentando adquirir o bloqueio no X). C2 conclui o método e libera o bloqueio. Agora, C1 adquire o bloqueio. Adivinhe, sorte, temos um loop while, porque, C1 realiza a verificação de loop (guarda) e é impedido de remover um elemento inexistente do buffer (C2 já entendi!). Se não tivéssemos um while , IndexArrayOutOfBoundsException um IndexArrayOutOfBoundsException enquanto C1 tentava remover o primeiro elemento do buffer!

AGORA,

Ok, agora por que precisamos de notifyAll?

No exemplo do produtor / consumidor acima, parece que podemos nos safar com a notify . Parece assim, porque podemos provar que os guardas nos loops de espera para produtor e consumidor são mutuamente exclusivos. Ou seja, parece que não podemos ter um encadeamento esperando no método put , bem como o método get , 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 tornar o exemplo fácil de seguir). As etapas a seguir nos levam ao impasse. Observe que, A QUALQUER MOMENTO que um encadeamento for chamado com notificar, ele poderá ser selecionado de forma não determinística pela JVM - ou seja, qualquer encadeamento em espera poderá ser acionado. Observe também que quando várias 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 que um thread só pode estar em um dos métodos a qualquer momento - os métodos sincronizados permitem que apenas um thread esteja executando (ou seja, mantendo o bloqueio de) qualquer método (sincronizado) na classe. Se a seguinte sequência de eventos ocorrer - resultados de impasse:

PASSO 1:
- P1 coloca 1 char no buffer

PASSO 2:
- P2 tentativas de put - verifica espera loop - já um char - aguarda

ETAPA 3:
- P3 tenta put - verifica espera loop - já um char - aguarda

PASSO 4:
- C1 tenta pegar 1 char
- C2 tenta obter 1 bloco de caracteres na entrada do método get
- C3 tenta obter 1 bloco de caracteres na entrada do método get

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

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

PASSO 7:
- A notificação de P2 desperta o P3 (lembre-se de qualquer tópico que possa ser acordado)
- P3 verifica a condição de loop de espera, já existe um caracter no buffer, então aguarda.
- NÃO MAIS LINHAS PARA CHAMAR NOTIFICAR E TRÊS ROSCANAS PERMANENTEMENTE SUSPENSAS!

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


De Joshua Bloch, o próprio Java Guru em Effective Java 2nd edition:

"Item 69: Preferir utilitários de simultaneidade para aguardar e notificar".


Espero que isso elimine algumas dúvidas.

notify() : O método notify () acorda um thread aguardando o bloqueio (o primeiro thread que chamou wait () naquele lock).

notify() : O método notifyAll () acorda todos os threads que estão aguardando 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 há mais de um encadeamento aguardando o bloqueio, em notify () e notifyAll (), o encadeamento exato acordado está sob o controle da JVM e você não pode controlar programaticamente a ativação de um encadeamento específico.

À primeira vista, parece que é uma boa ideia apenas chamar notify () para ativar um thread; pode parecer desnecessário acordar todos os tópicos. No entanto, o problema com notify () é que o encadeamento ativado 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 segmento será ativado potencialmente levando a um tipo de conflito (a notificação é perdida e todos os outros threads aguardam 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.


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

Por que usar notifyAll, mesmo quando cada produto é destinado a um único consumidor?

Considere os produtores e consumidores, simplificados da seguinte forma.

Produtor:

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

Consumidor:

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

Assume 2 produtores e 2 consumidores, compartilhando um buffer de tamanho 1. A figura a seguir descreve um cenário que leva a um deadlock , que seria evitado se todos os threads usassem notifyAll .

Cada notificação é rotulada com o segmento sendo ativado.


Eu acho que isso depende de como os recursos são produzidos e consumidos. Se 5 objetos de trabalho estiverem disponíveis de uma só vez e você tiver 5 objetos de consumidor, faria sentido ativar todos os encadeamentos usando notifyAll () para que cada um possa processar um objeto de trabalho.

Se você tem apenas um objeto de trabalho disponível, qual é o ponto de acordar todos os objetos de consumidor para correr para aquele objeto? O primeiro verificando o trabalho disponível irá obtê-lo e todos os outros tópicos irão verificar e descobrir que eles não têm nada para fazer.

Eu encontrei uma ótima explicação aqui . Em resumo:

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


Existem três estados para um segmento.

  1. WAIT - O encadeamento não está usando nenhum ciclo de CPU
  2. BLOQUEADO - O encadeamento está bloqueado ao tentar adquirir um monitor. Ele ainda pode estar usando os ciclos da CPU
  3. EXECUTANDO - O encadeamento está sendo executado.

Agora, quando um notify () é chamado, a JVM seleciona um thread e os move para o estado BLOCKED e, portanto, para o estado RUNNING, pois não há concorrência para o 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 em uma base de prioridade. A tarefa que é capaz de adquirir o monitor primeiro poderá ir para o estado RUNNING primeiro e assim por diante.


Todas as respostas acima estão corretas, tanto quanto eu posso dizer, então eu vou lhe dizer 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 simultaneidade em java.


notify() irá acordar um thread enquanto notifyAll() irá acordar todos. Tanto quanto sei, não há meio termo. Mas se você não tiver certeza do que o notify() fará com seus threads, use notifyAll() . Funciona como um charme toda vez.


notify()- Seleciona um encadeamento aleatório do conjunto de espera do objeto e o coloca no BLOCKEDestado. O restante dos segmentos no conjunto de espera do objeto ainda estão no WAITINGestado.

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

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


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

notifyAll()Acorda todos os threads que chamam wait()o mesmo objeto.

O segmento de maior prioridade será executado primeiro.


Pequeno resumo:

Sempre prefira notifyAll () over 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:

notify () [...] acorda um único thread. Como notify () não permite que você especifique o encadeamento que é ativado, ele é útil apenas em aplicativos massivamente paralelos - isto é, programas com um grande número de encadeamentos, todos executando tarefas semelhantes. Em tal aplicativo, você não se importa com qual segmento é acordado.

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á o despertar (isto é, agendamento) de um grande número de threads, muitos deles desnecessariamente (já que apenas um thread pode realmente continuar, a saber, o thread que receberá o thread). monitor para o objeto wait () , notify () ou notifyAll () foi chamado), portanto, desperdiçando recursos de computação.

Portanto, se você não tiver um aplicativo em que um grande número de threads faz a mesma coisa ao mesmo tempo, prefira notifyAll () over notify () . Por quê? Porque, como outros usuários já responderam neste fórum, notifique ()

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

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

Imagine que você tenha uma aplicação de consumidor do 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 (para 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 avançaria, embora produtores e consumidores estejam prontos para produzir e consumir, respectivamente. Em vez disso, um consumidor é acordado (ou seja, deixando o status wait () ), não tira um item da fila porque está vazio e notifica () outro consumidor para continuar.

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


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

Suponha que dois encadeamentos diferentes estejam aguardando duas condições diferentes:
O primeiro encadeamento está aguardando buf.size() != MAX_SIZEe o segundo encadeamento está aguardando buf.size() != 0.

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

O primeiro thread é acordado, verifica o buf.size()que pode retornar MAX_SIZEe volta para a espera. O segundo segmento não é acordado, continua a aguardar e não chama get().


Para resumir as excelentes explicações detalhadas acima, e da maneira mais simples que posso pensar, 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 esperada / notificada sobre / sobre.

Isso significa que se vários encadeamentos estiverem aguardando em condições diferentes e notify () for usado, o encadeamento selecionado pode não ser o que faria progresso na condição recém-preenchida - causando esse encadeamento (e outros encadeamentos atualmente em espera que poderiam para cumprir a condição, etc.) não ser capaz de progredir e, eventualmente, fome ou programa de desligamento.

Em contraste, notifyAll () permite que todos os encadeamentos 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 somente se qualquer thread em espera for garantido para permitir que o progresso seja feito, o que geralmente é satisfeito quando todos os threads dentro do mesmo monitor verificam apenas uma e a mesma condição - um bastante raro caso em aplicações do mundo real.


Quando você chama o wait () do "objeto" (esperando que o bloqueio de objeto seja adquirido), intern isto liberará o bloqueio naquele objeto e ajudará os outros threads a terem bloqueio neste "objeto", neste cenário haverá mais de 1 thread esperando pelo "resource / object" (considerando os outros threads também emitidos a espera no mesmo objeto acima e abaixo do caminho haverá um thread que preencherá o resource / object e invoca notify / notifyAll).

Aqui quando você emite a notificação do mesmo objeto (do mesmo lado / outro lado do processo / código), isso liberará um encadeamento único bloqueado e em espera (não todos os encadeamentos em espera - este encadeamento liberado será escolhido pelo encadeamento da JVM Agendador e todo o processo de obtenção de bloqueio no objeto é o mesmo que regular).

Se você tiver Apenas um thread que estará compartilhando / trabalhando neste objeto, não há problema em usar o método notify () sozinho em sua implementação wait-notify.

Se você estiver em situação em que mais de um thread lê e escreve em recursos / objetos com base em sua lógica de negócios, você deve ir para notifyAll ()

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


Acordar tudo não faz muito sentido aqui. aguarde notifique e notifique todos, todos estes são colocados depois de possuir o monitor do objeto. Se um encadeamento estiver no estágio de espera e a notificação for chamada, esse encadeamento assumirá o bloqueio e nenhum outro encadeamento nesse ponto poderá assumir esse bloqueio. Portanto, o acesso simultâneo não pode ocorrer de forma alguma. Tanto quanto eu sei qualquer chamada a esperar notificar e notificar todos podem ser feitos somente depois de tomar o bloqueio no objeto. Corrija-me se eu estiver enganado.


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 idéia de que se deve usar java.util.concurrent tanto quanto possível, em vez de tentar escrever o próprio código concorrente quebrado. De volta à pergunta: para resumir, a melhor prática hoje é EVITAR notify () em TODAS as situações devido ao problema de despertar perdido. Qualquer um que não entenda isso não deve ter permissão para escrever código de concorrência de missão crítica. Se você está preocupado com o problema de pastoreio, uma maneira segura de alcançar um thread de cada vez é: 1. Construir uma fila de espera explícita para os threads em espera; 2. Faça com que cada um dos threads na fila espere pelo seu predecessor; 3. Faça com que cada thread chame notifyAll () quando terminar. Ou você pode usar o Java.util.concurrent. *,que já implementaram isso.







multithreading