Um cenário simples usando wait () e notify () em java

181

Posso obter um cenário simples e completo, isto é, um tutorial que sugere como isso deve ser usado, especificamente com uma Fila?

Olaseni
fonte

Respostas:

269

Os métodos wait()e notify()são projetados para fornecer um mecanismo para permitir que um encadeamento seja bloqueado até que uma condição específica seja atendida. Para isso, suponho que você queira escrever uma implementação de fila de bloqueio, na qual você tenha algum armazenamento fixo de elementos de tamanho fixo.

A primeira coisa que você precisa fazer é identificar as condições que você deseja que os métodos esperem. Nesse caso, você desejará que o put()método seja bloqueado até que haja espaço livre na loja e que você deseje que o take()método seja bloqueado até que haja algum elemento para retornar.

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

Há algumas coisas a serem observadas sobre a maneira pela qual você deve usar os mecanismos de espera e notificação.

Primeiro, você precisa garantir que todas as chamadas para wait()ou notify()estejam dentro de uma região sincronizada do código (com as chamadas wait()e notify()sendo sincronizadas no mesmo objeto). A razão para isso (além das preocupações de segurança de linha padrão) deve-se a algo conhecido como sinal perdido.

Um exemplo disso é que um encadeamento pode chamar put()quando a fila estiver cheia e, em seguida, verifica a condição, vê que a fila está cheia, mas antes que possa bloquear outro encadeamento está agendado. Esse segundo encadeamento take()é um elemento da fila e notifica os encadeamentos em espera de que a fila não está mais cheia. No entanto, como o primeiro encadeamento já verificou a condição, ele simplesmente chamará wait()após ser remarcado, mesmo que possa progredir.

Ao sincronizar em um objeto compartilhado, você pode garantir que esse problema não ocorra, pois a chamada do segundo encadeamento take()não poderá progredir até que o primeiro encadeamento seja realmente bloqueado.

Em segundo lugar, você precisa colocar a condição que está verificando em um loop while, em vez de uma instrução if, devido a um problema conhecido como despertares espúrios. É aqui que um segmento em espera às vezes pode ser reativado sem notify()ser chamado. Colocar essa verificação em um loop while garantirá que, se ocorrer uma ativação espúria, a condição será verificada novamente e o thread chamará wait()novamente.


Como algumas das outras respostas mencionaram, o Java 1.5 introduziu uma nova biblioteca de simultaneidade (no java.util.concurrentpacote) que foi projetada para fornecer uma abstração de nível mais alto através do mecanismo de espera / notificação. Usando esses novos recursos, você pode reescrever o exemplo original da seguinte maneira:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Obviamente, se você realmente precisa de uma fila de bloqueio, use uma implementação da interface BlockingQueue .

Além disso, para coisas como essa, eu recomendo o Java Concurrency in Practice , pois abrange tudo o que você poderia querer saber sobre problemas e soluções relacionados à concorrência.

Jared Russell
fonte
7
@ Greuze, notifyacorda apenas um segmento. Se dois segmentos do consumidor estiverem competindo para remover um elemento, uma notificação poderá ativar o outro segmento do consumidor, que não pode fazer nada a respeito e voltará a dormir (em vez do produtor, que esperávamos que fosse inserir um novo elemento). o encadeamento do produtor não é ativado, nada é inserido e agora os três encadeamentos ficam suspensos indefinidamente. Tirei meu comentário anterior como disse (erroneamente) que despertar espúria foi a causa do problema (Não é.)
finnw
1
@finnw Até onde eu sei, o problema que você viu pode ser resolvido usando o notifyAll (). Estou certo?
Clint Eastwood
1
O exemplo dado aqui por @Jared é muito bom, mas tem uma queda séria. No código, todos os métodos foram marcados como sincronizados, mas NENHUM DOIS MÉTODOS SINCRONIZADOS PODEM SER EXECUTADOS AO MESMO TEMPO, então como é que existe um segundo segmento na imagem.
Shivam Aggarwal
10
@ Brut3Forc3, você precisa ler o javadoc de wait (): diz: O thread libera a propriedade deste monitor . Portanto, assim que wait () é chamado, o monitor é liberado e outro encadeamento pode executar outro método sincronizado da fila.
JB Nizet
1
@JBNizet. "Um exemplo disso é que um thread pode chamar put () quando a fila estiver cheia, depois verifica a condição, vê que a fila está cheia, mas antes que possa bloquear outro thread está agendado". o segundo segmento está programado se espera ainda não foi chamado.
Shivam Aggarwal
148

Não é um exemplo de fila, mas extremamente simples :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

Alguns pontos importantes:
1) NUNCA faça

 if(!pizzaArrived){
     wait();
 }

Sempre use while (condição), porque

  • a) os threads podem esporadicamente acordar do estado de espera sem serem notificados por ninguém. (mesmo quando o entregador da pizza não tocava a campainha, alguém decidia tentar comer a pizza.).
  • b) Você deve verificar a condição novamente depois de adquirir o bloqueio sincronizado. Digamos que a pizza não dure para sempre. Você acorda, faz fila para a pizza, mas não é suficiente para todos. Se você não verificar, você pode comer papel! :) (provavelmente melhor exemplo seria while(!pizzaExists){ wait(); }.

2) Você deve segurar o bloqueio (sincronizado) antes de chamar wait / nofity. Os threads também precisam adquirir bloqueio antes de acordar.

3) Tente evitar adquirir qualquer bloqueio dentro do bloco sincronizado e tente não invocar métodos alienígenas (métodos que você não sabe ao certo o que estão fazendo). Se for necessário, tome medidas para evitar conflitos.

4) Tenha cuidado com notify (). Continue com notifyAll () até saber o que está fazendo.

5) Por último, mas não menos importante, leia Java Concurrency in Practice !

Enno Shioji
fonte
1
Você poderia explicar por que não usar "if (! PizzaArrived) {wait ();}"?
Todos
2
@ Todo mundo: Adicionado alguma explicação. HTH.
Enno Shioji 29/03
1
por que usar a pizzaArrivedbandeira? se o sinalizador for alterado sem uma chamada notify, não terá nenhum efeito. Também apenas com waite notifychama o exemplo funciona.
Pablo Fernandez
2
Eu não entendo - o thread 1 executa o método eatPizza () e entra no bloco sincronizado superior e sincroniza na classe MyHouse. Ainda não chegou pizza, por isso aguarda. Agora o segmento 2 tenta entregar a pizza chamando o método pizzaGuy (); mas não pode, pois o segmento 1 já possui o bloqueio e não o desiste (está sempre aguardando). Efetivamente, o resultado é um impasse - o thread 1 está aguardando o thread 2 executar o método notifyAll (), enquanto o thread 2 está aguardando o thread 1 abrir mão do bloqueio na classe MyHouse ... O que está faltando? aqui?
685682
1
Não, quando uma variável é guardada por synchronizedpalavra-chave, é redundante para declarar a variável volatile, e recomenda-se para evitá-lo, para evitar confusão @mrida
Enno Shioji
37

Mesmo que você tenha solicitado wait()e notify()especificamente, sinto que essa citação ainda é importante o suficiente:

Josh Bloch, Effective Java 2nd Edition , Item 69: Prefere utilitários de simultaneidade como waite notify(ênfase dele):

Dada a dificuldade de usar waite notifycorretamente, você deve usar os utilitários de simultaneidade de nível superior em vez [...] de usar waite notifydiretamente é como programar na "linguagem de montagem de simultaneidade", em comparação com a linguagem de nível superior fornecida por java.util.concurrent. Raramente, se é que existe, motivo para usar waite notifyno novo código .

poligenelubricants
fonte
O BlockingQueueS fornecido no pacote java.util.concurrent não é persistente. O que podemos usar quando a fila precisa ser persistente? ou seja, se o sistema ficar com 20 itens na fila, preciso que eles estejam presentes quando o sistema reiniciar. Como as filas java.util.concurrent parecem estar 'na memória', existe alguma maneira de usá-las como / hackeadas / substituídas para fornecer implementações com capacidade de persistência?
Volksman
1
Talvez a fila de suporte possa ser fornecida? ou seja, forneceríamos uma implementação de interface de fila persistente.
Volksman
Isto é muito bom para mencionado neste contexto que você nunca precisará usar o notify()e wait()novamente
Chaklader Asfak Arefe
7

Você deu uma olhada neste tutorial em Java ?

Além disso, aconselho que você evite brincar com esse tipo de coisa em software real. É bom brincar com ele para que você saiba o que é, mas a concorrência tem armadilhas em todo o lugar. É melhor usar abstrações de nível superior e coleções sincronizadas ou filas JMS se você estiver criando um software para outras pessoas.

É pelo menos o que eu faço. Como não sou especialista em concorrência, evito manipular os threads manualmente sempre que possível.

extraneon
fonte
2

Exemplo

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}
Ferhat KOÇER
fonte
0

Exemplo para wait () e notifyall () em Threading.

Uma lista de matriz estática sincronizada é usada como recurso e o método wait () é chamado se a lista de matriz estiver vazia. O método notify () é chamado depois que um elemento é adicionado à lista de matrizes.

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}
srinivas
fonte
1
plz verifique essa linha if(arrayList.size() == 0), acho que pode ser um erro aqui.
Wizmann 2/11/16