详解生产者消费者问题 wait notify 附生产者唤醒生产者程序运行不下去的问题

生产者和消费者是线程间进行通信的典型例子。实质是多线程共同操作一缓冲队列。

Synchronized + wait() notify() 实现,引出一个问题

  • notify只负责唤醒所在对象的阻塞线程中的一个。被唤醒的线程进入可运行状态,等待cpu时间片。

  • notifyAll负责唤醒所在对象的所有阻塞线程,即所有因为对象阻塞的线程处于唤醒状态,其中一个线程会获得锁,当该线程退出后,其他线程继续竞争。

  • 如果用notify()只唤醒一个的话,程序会执行不下去。这种情况不是死锁。是对象调用wait()方法后,线程进入等待队列,如果不唤醒,不能自己唤醒。这里涉及到线程状态和wait(),notify()方法。

下面详细讲解下wait()和notify()方法。

  1. wait()方法

    1. 和notify()一样都不是线程的方法,在Object超类中就有该方法,所有的实现类都继承自Object,所以他们会重写该方法。所以代码中的this.wait()属于Buffer的实现类buffer,它是多线程共享的类的方法。所以wait(),notify()的前提是获得对象的锁,即在同步块内使用。
    2. 执行wait()后当前线程进入等待队列,等待被唤醒,唤醒方法可以是notify和notifyAll。唤醒后的线程与其他线程一起竞争该对象的锁,如果获得锁,从wait()方法后面直接运行,这里会做代码实现,清下下面代码的输出,下面会做分析。执行wait()后当前线程进入等待队列,等待被唤醒,唤醒方法可以是notify和notifyAll。唤醒后的线程与其他线程一起竞争该对象的锁,如果获得锁,从wait()方法后面直接运行,这里会做代码实现,清下下面代码的输出,下面会做分析。
  2. notify()方法

    1. 只唤醒该对象等待队列中的一个线程,随机唤醒。唤醒后执行notify()后面的代码,出了同步块后即释放锁。锁池中的线程再竞争该锁。其他的等待队列中的线程还是在等待队列。这里与notifyAll有区别。
  3. notifyAll()方法

    1. 唤醒所有等待队列中的线程,进入锁池,一起竞争该对象。当某一个线程获得锁,运行完毕后释放锁或,锁池中的锁还可以继续竞争。

这里有段深入理解写的很好,我贴出来
http://blog.csdn.net/ns_code/article/details/17225469
深入理解:
如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

请看下面代码

public class Main{
    public static void main(String[] arg){
        Buffer buffer=new Buffer(5);
        Producer producer=new Producer(buffer);
        Consumer consumer=new Consumer(buffer);
        //创建线程执行生产和消费
        for(int i=0;i<1;i++){
            new Thread(producer,"producer-"+i).start();
        }
        for(int i=0;i<10;i++){
            new Thread(consumer,"consumer-"+i).start();
        }
    }
}
class Buffer {
    private int maxSize;
    private List<Date> storage;
    Buffer(int size){
        maxSize=size;
        storage=new LinkedList<>();
    }
    //生产方法
    public synchronized void put()  {
        try {
            System.out.println("同步块开始运行处");
            while (storage.size() ==maxSize ){//如果队列满了
                System.out.println("看生产者从哪开始");
                System.out.print(Thread.currentThread().getName()+": wait \n");;
                this.wait();//阻塞线程
            }
            storage.add(new Date());
            System.out.print(Thread.currentThread().getName()+": put:"+storage.size()+ "\n");
            Thread.sleep(1000);
            this.notify();//唤起线程
            //this.notifyAll();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    //消费方法
    public synchronized void take() {
        try {
            while (storage.size() ==0 ){//如果队列空了
                System.out.print(Thread.currentThread().getName()+": wait \n");
                this.wait();//阻塞线程
            }
            Date d=((LinkedList<Date>)storage).poll();
            System.out.print(Thread.currentThread().getName()+": take:"+storage.size()+ "\n");
            Thread.sleep(1000);
            this.notify();//唤起线程
            //this.notifyAll();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
//生产者
class Producer implements Runnable{
    private Buffer buffer;
    Producer(Buffer b){
        buffer=b;
    }
    @Override
    public void run() {
        while(true){
            buffer.put();
        }
    }
}
//消费者
class Consumer implements Runnable{
    private Buffer buffer;
    Consumer(Buffer b){
        buffer=b;
    }
    @Override
    public void run() {
        while(true){
            buffer.take();
        }
    }
}

输出:同步块开始运行处
producer-0: put:1
同步块开始运行处
producer-0: put:2
同步块开始运行处
producer-0: put:3
同步块开始运行处
producer-0: put:4
同步块开始运行处
producer-0: put:5
同步块开始运行处
看生产者从哪开始
producer-0: wait
consumer-8: take:4
consumer-8: take:3
consumer-8: take:2
consumer-8: take:1
consumer-8: take:0
consumer-8: wait
consumer-9: wait
consumer-6: wait
consumer-5: wait
consumer-3: wait
consumer-7: wait
consumer-4: wait
consumer-2: wait
consumer-0: wait
consumer-1: wait
producer-0: put:1
同步块开始运行处
producer-0: put:2
同步块开始运行处
producer-0: put:3
同步块开始运行处
producer-0: put:4
同步块开始运行处
producer-0: put:5
同步块开始运行处
看生产者从哪开始
producer-0: wait
consumer-8: take:4
consumer-8: take:3
consumer-8: take:2
consumer-8: take:1
consumer-8: take:0
consumer-8: wait
consumer-7: wait
consumer-1: wait
consumer-0: wait
consumer-2: wait
consumer-4: wait
consumer-3: wait
consumer-5: wait
consumer-6: wait
consumer-9: wait

这是典型的生产者和消费者代码,这段代码贴的是这个博客的例子,写的很好。
http://blog.csdn.net/chenchaofuck1/article/details/51592429

但是在执行该代码的时候,我发现一个问题,如果用notify()方法,我有一个线程运行生产者代码,10个线程运行消费者代码。缓冲区最多是5个产品。
从输出结果如下,然后程序就不运行了。下面根据输出结果分析下这段代码:
同步块开始运行处
producer-0: put:1
同步块开始运行处
producer-0: put:2
同步块开始运行处
producer-0: put:3
同步块开始运行处
producer-0: put:4
同步块开始运行处
producer-0: put:5
生产者线程连续5次获得了锁,因为put()方法嵌套这while循环,所以生产者线程再制作完了一个对象后会释放锁,和消费者线程一个竞争。可能之前进入过该对象,使得该线程获得锁的概率比较高,这里有待进一步研究!

同步块开始运行处
看生产者从哪开始
producer-0: wait
第六次进入该对象,此时已经不能生产了,运行wait()方法,进入等待队列,注意此时等待队列只有一个生产者线程。消费者线程都在锁池中。
此时消费者线程竞争该对象,消费者线程8获得该对象。注意根据代码,线程8在第一次消耗产品的时候就执行了notify方法,此时等待队列的生产者线程已经被唤醒了。一次参与buffer对象的竞争。线程8连续获得锁。

consumer-8: take:4
consumer-8: take:3
consumer-8: take:2
consumer-8: take:1
consumer-8: take:0
consumer-8: wait
都消耗完了,线程8进入等待队列。此时其他的线程也竞争到了对象锁,但是没有产品了,运行wait方法,进入等待队列。
consumer-9: wait
consumer-6: wait
consumer-5: wait
consumer-3: wait
consumer-7: wait
consumer-4: wait
consumer-2: wait
consumer-0: wait
consumer-1: wait

生产者线程获得锁,开始加工产品,没加工一个产品唤醒一个等待队列中的线程。注意下面的此处,是从producer-0: put:1输出的,没有输出同步块开始运行处。这证明了被唤醒的线程获得锁后是从wait()返回处开始运行的。

此时连续加工5个产品,已经有5个线程被唤醒了。
生产者线程执行wait(),放弃锁。
producer-0: put:1
同步块开始运行处
producer-0: put:2
同步块开始运行处
producer-0: put:3
同步块开始运行处
producer-0: put:4
同步块开始运行处
producer-0: put:5
同步块开始运行处
看生产者从哪开始
producer-0: wait

消费者线程8获得锁,执行消费任务, 没消费一次,运行一次notify方法,唤醒一个锁。从后面的运行结果来看,没有唤醒生产者线程。被唤醒的消费者线程竞争锁,因为没有产品,执行wait()方法,然后所有的线程都在等待队列了。
consumer-8: take:4
consumer-8: take:3
consumer-8: take:2
consumer-8: take:1
consumer-8: take:0
consumer-8: wait
consumer-7: wait
consumer-1: wait
consumer-0: wait
consumer-2: wait
consumer-4: wait
consumer-3: wait
consumer-5: wait
consumer-6: wait
consumer-9: wait

这里比较凑巧,我选了生产5个产品,有10个消费者线程。如果有10个产品,10个消费者线程,生产产品能唤醒10个线程,消耗产品能唤醒10个线程,就不会出现上面的问题了,因为肯定能把所有的线程都唤醒。
如果我设置只生产3个产品,有10个消费者线程,后面就不会这么巧,所有的消费者线程都竞争了一次锁了。输出如下:
同步块开始运行处
producer-0: put:1
同步块开始运行处
producer-0: put:2
同步块开始运行处
producer-0: put:3
同步块开始运行处
看生产者从哪开始
producer-0: wait
consumer-9: take:2
consumer-9: take:1
consumer-9: take:0
consumer-9: wait
consumer-8: wait
consumer-7: wait
consumer-6: wait
consumer-4: wait
consumer-5: wait
consumer-3: wait
consumer-2: wait
consumer-1: wait
consumer-0: wait
producer-0: put:1
同步块开始运行处
producer-0: put:2
同步块开始运行处
producer-0: put:3
同步块开始运行处
看生产者从哪开始
producer-0: wait
consumer-9: take:2
consumer-9: take:1
consumer-9: take:0
consumer-9: wait
consumer-6: wait
consumer-5: wait
consumer-4: wait
consumer-7: wait
consumer-8: wait

以上用notifyAll不用考虑生产者唤醒生产者,消费者唤醒消费者的问题了。

你可能感兴趣的:(并发编程)