首先了解notify()和wait()的功能是什么?
wait()和notify()是每个对象都有的两个方法。考虑在两个线程的问题中,对象obj在这两个线程中都可以引用,在一个线程中,对象obj调用wait()方法导致线程阻塞,该线程在执行到这一行后便停止等待,直到另一线程中该obj对象调用notify()方法后才开始继续运行wait()之后的代码。
notify()的功能是随机激活一个在该对象下阻塞的线程。如果是两个线程中的对象都调用了wait()方法后都阻塞等待,那么另一个线程中的对象调用notify()后,只是随机的通知前面两个阻塞的线程中的一个线程激活,要使所有的阻塞线程都激活可以用notifyAll()方法。
wait()方法要写在try{}语句里。不仅,还要写在synchronized(){}语句里,因为当想要调用wait( )进行线程等待时,必须要取得这个锁对象的控制权,防止其他线程修改状态。
生产者消费者模型的建立
现考虑这样一种情况:有一个栈s,第一个线程只负责往这个栈s里放数据,也就是执行push操作。第二个线程只负责从这个栈s中取数据,也就是执行pop操作。那么现在第一个线程就像一个生产者一样,负责生产数据,而第二个线程就像消费者一样,消耗数据。这就是生产者消费模型,他们都对一块公共的数据进行操作。
那么这种模型就会产生这么几种情况:
- 生产者的生产速度和消费者的消费速度一样。
- 生产者生产速度大于消费者消费速度。
- 生产者生产速度小于消费者消费速度。
第一种情况的话,工作的很协调。
第二种情况随着时间的推移,栈的空间会被存满,那么这个时候生产者就不能再生产数据了,需等到消费者消费了数据之后才能生产数据。如果生产者使用if语句判断栈的空间有无剩余的话,行是能行,只不过这个线程一直在运行if语句,还在占用系统资源,对系统性能有很大影响。有一种比较好的方法就是:当空间满的话,生产者就等待,线程阻塞,释放资源,等待消费者消耗了数据的时候,再激活生产者线程。
反过来,第三种情况当栈s空时,消费者便进入等待,线程阻塞,如果生产者生产了数据后,再激活消费者线程。
最后贴上我的测试代码。
生产者:
import java.util.Stack;
public class Producer extends Thread {
public Stack<Integer> s;
public Producer(Stack<Integer> s) {
this.s = s;
}
@Override
public void run() {
while(true) {
synchronized(s)
{
if(s.size()>30) {
try {
System.out.println("producer wait");
s.wait();
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
s.push(s.size());
System.out.println("生产者:"+"push:"+s.size());
s.notify();
}
}
}
}
消费者:
import java.util.Stack;
public class Consumer extends Thread {
public Stack<Integer> s;
public Consumer(Stack<Integer> s) {
this.s = s;
}
@Override
public void run() {
while(true)
{
synchronized(s)
{
if(s.size()==0)
{
try {
System.out.println("消费者等待");
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费:"+(s.pop()+1));
s.notify();
}
}
}
}
测试类:
import java.util.Stack;
public class Main {
public static Stack<Integer> s =new Stack<>();
public static void main(String[] args) {
System.out.println(s.size());
Producer p = new Producer(s);
Consumer c = new Consumer(s);
p.start();
c.start();
}
}