JAVA多线程wait与notify详细解析(由生产者和消费者案例引出)

生产者和消费者这个关系是个经典的多线程案例。现在我们编写一个Demo来模拟生产者和消费者之间的关系。

假如有两个类,一个是数据生产者类DataProvider,另一个是数据消费者类DataConsumer,这两个类同时对数据类Data进行操作,生产者类负责生产数据,消费者类负责消费数据,下面是对这个过程的描述。

class DataProvider implements Runnable{
	private Data data;
	public DataProvider(Data data) {
		this.data=data;
	}
	public void run() {
		for(int i=0;i<=50;i++) {
			if(i%2==0) {
				data.setName("张三");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				data.setTag("---学生");
			}else {
				data.setName("李四");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				data.setTag("---老师");
			}
		}
	}
}
class DataConsumer implements Runnable{
	private Data data;
	public DataConsumer(Data data) {
		this.data=data;
	}
	public void run() {
		for(int i=0;i<50;i++) {
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(data.getName()+data.getTag());
		}
	}
}
class Data{
	private String name;
	private  String tag;
	public void setName(String name) {
		this.name=name;
	}
	public void setTag(String tag) {
		this.tag=tag;
	}
	public String getName() {
		return this.name;
	}
	public String getTag() {
		return this.tag;
	}
}


public class TestDemo{
	public static void main(String args[]) {
		Data data=new Data();
		new Thread(new DataProvider(data)).start();
		new Thread(new DataConsumer(data)).start();
	}
}

输出如下:

张三null
张三null
张三null
张三null
李四---学生
李四---学生
李四---学生
李四---学生
李四---学生
张三---老师
张三---老师
张三---老师
张三---老师
张三---老师
李四---学生
李四---学生
李四---学生
李四---学生
李四---学生
张三---老师
张三---老师
张三---老师

有输出可以发现问题:张三的值一会是空一回是老师学生,李四的值也发生了变化。这种操作不同步数据有偏差的问题是什么原因导致的呢?这是因为以前我们经常写的代码都是由主方法调用的,但是上面的这个代码却是由多个线程对我们的类进行操作,所以问题就产生了。

出现了上述问题我们第一个想到的就是使用synchronized关键字来解决。下面对上述代码进行修改。

class DataProvider implements Runnable{
	private Data data;
	public DataProvider(Data data) {
		this.data=data;
	}
	public void run() {
		for(int i=0;i<=50;i++) {
			if(i%2==0) {
				data.set("张三","是一个学生");
			}else {
				data.set("李四","是一个老师");
			}
		}
	}
}
class DataConsumer implements Runnable{
	private Data data;
	public DataConsumer(Data data) {
		this.data=data;
	}
	public void run() {
		for(int i=0;i<50;i++) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				data.get();
		}
	}
}
class Data{
	private String name;
	private String tag;
	private boolean flag=true;
	public synchronized void set(String name,String tag) {
		this.name=name;
		this.tag=tag;
	}
	public synchronized void get() {
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.name+"----"+this.tag);
		
	}
}


public class TestDemo{
	public static void main(String args[]) {
		Data data=new Data();
		new Thread(new DataProvider(data)).start();
		new Thread(new DataConsumer(data)).start();
	}
}

输出结果如下:

张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是一个学生
张三----是

修改之后数据之间不会乱了,但是这样会出现一个更加严重的问题,那就是数据重复更加严重了。synchronized能解决的只是一个同步问题,但是无法解决数据交替问题(注释两点内容:以上两个输出结果都是只截取了片段,没有完整截取。其次就是根据电脑的运行速度,输出的结果会有所不同)。

出现上述现象应该怎样来解决呢?实际上我们可以考虑使用Object类里面的wait()来让线程进行等待。当生产者线程没有执行完的时候,Data这个类的门牌上亮的是红灯,当生产者类生产完数据的时候Data这个类才亮绿灯,表示可以取走数据了。

Object类里面的wait()方法有两个wait()是死等,意思就说只要不唤醒就一直在哪等着,但是还有一个参数是long型的wait是活等,等够设置的时间就自动唤醒。既然有等待线程,那么就会有唤醒线程,唤醒线程主要由两个:notify和notifyAll两个,notify只是唤醒第一个等待的线程,而notifyAll则是唤醒所有等待的线程,至于一次性唤醒这么多到底谁先执行?谁的优先级高谁先执行。

下面来看怎么使用java等待唤醒机制来解决上面出现的问题

class DataProvider implements Runnable{
	private Data data;
	public DataProvider(Data data) {
		this.data=data;
	}
	public void run() {
		for(int i=0;i<=50;i++) {
			if(i%2==0) {
				this.data.set("张三","是一个学生");
			}else {
				this.data.set("李四","是一个老师");
			}
		}
	}
}
class DataConsumer implements Runnable{
	private Data data;
	public DataConsumer(Data data) {
		this.data=data;
	}
	public void run() {
		for(int i=0;i<50;i++) {
			this.data.get();
		}
	}
}
class Data{
	private String name;
	private String tag;
	//flag=true表示允许生产但是不允许消费者取走
	//flag=false表示允许取走但是不允许生产者生产
	private boolean flag=false;
	public synchronized void set(String name,String tag) {
		if(flag==true) {
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.name=name;
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.tag=tag;
		this.flag=true;
		super.notify();
	}
	public synchronized void get() {
		if(flag==false) {//正在生产,不能取走
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.name+"----"+this.tag);
		this.flag=false;//能够取走,此时不能再生产了
		super.notify();//唤醒有可能正在等待的其他线程
	}
}


public class TestDemo{
	public static void main(String args[]) {
		Data data=new Data();
		new Thread(new DataProvider(data)).start();
		new Thread(new DataConsumer(data)).start();
	}
}

输出结果:

张三----是一个学生
李四----是一个老师
张三----是一个学生
李四----是一个老师
张三----是一个学生
李四----是一个老师
张三----是一个学生
李四----是一个老师
张三----是一个学生
李四----是一个老师
张三----是一个学生
李四----是一个老师
张三----是一个学生
李四-

如结果,上面出现的问题得到了很好的解决。这就是线程休眠和唤醒的详细解释。

下面是彩蛋:

sleep()和wait()的区别:

sleep是Thread类里面定义的方法,到了一定时间可以自动唤醒。

wait是Object类定义的方法,如果要想唤醒必须使用notify方法或者notifyAll方法才能唤醒

猜你喜欢

转载自blog.csdn.net/yaoyaoyao_123/article/details/84678051
今日推荐