生产者和消费者是线程间进行通信的典型例子。实质是多线程共同操作一缓冲队列。
notify只负责唤醒所在对象的阻塞线程中的一个。被唤醒的线程进入可运行状态,等待cpu时间片。
notifyAll负责唤醒所在对象的所有阻塞线程,即所有因为对象阻塞的线程处于唤醒状态,其中一个线程会获得锁,当该线程退出后,其他线程继续竞争。
如果用notify()只唤醒一个的话,程序会执行不下去。这种情况不是死锁。是对象调用wait()方法后,线程进入等待队列,如果不唤醒,不能自己唤醒。这里涉及到线程状态和wait(),notify()方法。
下面详细讲解下wait()和notify()方法。
wait()方法
notify()方法
notifyAll()方法
这里有段深入理解写的很好,我贴出来
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不用考虑生产者唤醒生产者,消费者唤醒消费者的问题了。