我能得到一个完整的简单场景,即教程,建议这应该如何使用,特别是与队列?


当前回答

线程中wait()和notifyall()的例子。

一个同步的静态数组列表被用作资源,如果数组列表为空,则调用wait()方法。一旦为数组列表添加了元素,就调用Notify()方法。

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

其他回答

wait()和notify()方法旨在提供一种机制,允许线程阻塞,直到满足特定条件。为此,我假设您想要编写一个阻塞队列实现,其中有一些固定大小的元素后置存储。

您必须做的第一件事是确定您希望方法等待的条件。在这种情况下,您将希望put()方法阻塞,直到存储中有空闲空间,并且您将希望take()方法阻塞,直到有一些元素要返回。

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

关于必须使用等待和通知机制的方式,有几件事需要注意。

首先,您需要确保对wait()或notify()的任何调用都在代码的同步区域内(wait()和notify()调用在同一个对象上同步)。其原因(与标准线程安全问题不同)是由于被错过的信号。

举个例子,一个线程可以在队列满的时候调用put(),然后检查条件,看到队列满了,但是在它阻塞另一个线程之前。第二个线程然后从队列中获取()的一个元素,并通知等待的线程队列不再满。但是,因为第一个线程已经检查了条件,所以在重新调度之后,它将简单地调用wait(),即使它可以取得进展。

通过在共享对象上同步,可以确保不会发生这个问题,因为第二个线程的take()调用在第一个线程实际阻塞之前无法进行。

其次,您需要将检查的条件放在while循环中,而不是if语句中,这是由于所谓的伪唤醒问题。在这里,等待线程有时可以在不调用notify()的情况下重新激活。将此检查放在while循环中将确保如果出现虚假唤醒,将重新检查条件,并且线程将再次调用wait()。


正如其他一些回答所提到的,Java 1.5引入了一个新的并发库(在Java .util.concurrent包中),其设计目的是在等待/通知机制之上提供更高级别的抽象。使用这些新特性,你可以像这样重写原来的例子:

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

当然,如果您确实需要一个阻塞队列,那么您应该使用BlockingQueue接口的实现。

此外,对于这些内容,我强烈推荐Java并发实践,因为它涵盖了与并发相关的问题和解决方案的所有内容。

不是一个队列的例子,但非常简单:)

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

以下要点: 1)永远不要做

 if(!pizzaArrived){
     wait();
 }

总是使用while(condition),因为

A)线程偶尔会被唤醒 从等待状态变为无存在状态 任何人都可以通知。(即使当 送披萨的没敲钟 有人会决定试试吃 披萨)。 b)你应该检查 条件再次获得后 同步锁。比如说披萨 不要永远存在。你保持清醒, 排队买披萨,但不是 每个人都能吃。如果你没有 检查,你可能会吃到纸!:) (也许更好的例子是 而(! pizzaExists) {wait ();}。

2)在调用wait/nofity之前,你必须持有锁(synchronized)。线程还必须在唤醒前获得锁。

3)尽量避免在同步块中获得任何锁,并努力不调用外来方法(你不确定它们在做什么的方法)。如果必须这样做,请确保采取措施避免死锁。

4)小心使用notify()。坚持使用notifyAll()直到你知道你在做什么。

5)最后,但并非最不重要的,阅读Java并发实践!

你看过这个Java教程吗?

此外,我建议您在真正的软件中远离这类东西。使用它很好,这样您就知道它是什么,但是并发性到处都有陷阱。如果您正在为其他人构建软件,最好使用更高级别的抽象和同步集合或JMS队列。

至少我是这么做的。我不是并发专家,所以我尽量避免手工处理线程。

即使你特别要求wait()和notify(),我觉得这句话仍然足够重要:

Josh Bloch, Effective Java第二版,第69项:更喜欢并发实用程序来等待和通知(强调他的):

考虑到正确使用等待和通知的困难,您应该使用更高级别的并发实用程序[…]与java.util.concurrent提供的高级语言相比,直接使用wait和notify就像用“并发汇编语言”编程。很少有理由在新代码中使用wait和notify。

线程中wait()和notifyall()的例子。

一个同步的静态数组列表被用作资源,如果数组列表为空,则调用wait()方法。一旦为数组列表添加了元素,就调用Notify()方法。

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