Pergunta

Posso obter um cenário simples e completo, ou seja,tutorial que sugere como isso deve ser usado, especificamente com um Queue?

Foi útil?

Solução

o wait() e notify() Os métodos são projetados para fornecer um mecanismo para permitir que um encadeamento bloqueie até que uma condição específica seja atendida. Para isso, presumo que você queira escrever uma implementação de fila de bloqueio, onde você tem uma loja de elementos de backing de tamanho fixo.

A primeira coisa que você precisa fazer é identificar as condições que deseja que os métodos aguardem. Nesse caso, você deseja o put() método para bloquear até que haja espaço livre na loja, e você deseja o take() Método para bloquear 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 como você deve usar os mecanismos de espera e notificar.

Em primeiro lugar, você precisa garantir que quaisquer chamadas para wait() ou notify() estão dentro de uma região sincronizada de código (com o wait() e notify() chamadas sendo sincronizadas no mesmo objeto). A razão para isso (exceto as preocupações padrão de segurança do encadeamento) é devido a algo conhecido como sinal perdido.

Um exemplo disso é que um tópico pode chamar put() Quando a fila está cheia, ela verifica a condição, vê se a fila está cheia, no entanto, antes que possa bloquear outro thread. Este segundo tópico então take()É um elemento da fila e notifica os fios de espera de que a fila não está mais cheia. Como o primeiro tópico já verificou a condição, no entanto, ele simplesmente liga wait() Depois de ser re-agendado, mesmo que possa progredir.

Ao sincronizar em um objeto compartilhado, você pode garantir que esse problema não ocorra, pois o segundo tópico take() A chamada não poderá progredir até que o primeiro thread tenha bloqueado.

Em segundo lugar, você precisa colocar a condição que está verificando em um tempo, em vez de uma instrução IF, devido a um problema conhecido como alerta espúria. É aqui que um fio de espera às vezes pode ser reativado sem notify() sendo chamado. Colocar essa verificação em um tempo, o loop garantirá que, se ocorrer um despertar espúrio, a condição será verificada novamente e o tópico ligará wait() novamente.


Como algumas das outras respostas mencionaram, o Java 1.5 introduziu uma nova biblioteca de concorrência (no java.util.concurrent pacote) projetado para fornecer uma abstração de nível mais alta sobre o mecanismo de espera/notificação. Usando esses novos recursos, você pode reescrever o exemplo original como assim:

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 precisar de uma fila de bloqueio, deve usar uma implementação do BlockingQueue interface.

Além disso, para coisas assim, eu recomendo Concorrência de java na prática, pois abrange tudo o que você pode querer saber sobre problemas e soluções relacionadas à simultaneidade.

Outras dicas

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 acordados do estado de espera sem serem notificados por ninguém.(Mesmo quando o cara da pizza não ligou para o carrilhão, alguém decidiu tentar comer a pizza.).
  • b) Você deve verificar a condição novamente após a aquisição do bloqueio sincronizado.Digamos que a pizza não dure para sempre.Você acordou, formando a pizza, mas não é suficiente para todos.Se você não verificar, pode comer papel!:) (provavelmente melhor exemplo seriawhile(!pizzaExists){ wait(); }.

2) Você deve manter o bloqueio (sincronizado) antes de invocar wait/nofity.Os threads também precisam adquirir bloqueio antes de serem ativados.

3) Tente evitar adquirir qualquer bloqueio dentro do seu bloco sincronizado e se esforce para 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 impasses.

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

5) Por último, mas não menos importante, leia Simultaneidade Java na prática!

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

Josh Bloch, 2ª edição eficaz Java, Item 69: prefira utilitárias de concorrência a wait e notify (ênfase dele):

Dada a dificuldade de usar wait e notify Corretamente, você deve usar os utilitários de simultaneidade de nível superior ...] usando wait e notify diretamente é como a programação em "linguagem de montagem de simultaneidade", em comparação com a linguagem de nível superior fornecida por java.util.concurrent. Raramente há, se alguma vez, razão para usar wait e notify em novo código.

Você já deu uma olhada nisso Java Tutorial?

Além disso, eu aconselho você a ficar longe de brincar com esse tipo de coisa em software real. É bom brincar com ele para que você saiba o que é, mas a simultaneidade 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 software para outras pessoas.

Isso é pelo menos o que eu faço. Não sou especialista em simultaneidade, então fico longe de lidar com linhas manualmente, sempre que possível.

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
     }

}

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

Uma lista de arrays estáticos sincronizados é usada como recurso e o método wait() é chamado se a lista de arrays estiver vazia.O método notify() é invocado quando um elemento é adicionado à lista de array.

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());
   }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top