线程同步--生产者与消费者

1、在线程操作中有一个经典案例程序,即生产者和消费者问题,生产者不断生产,消费者不断取走生产者生产的产品。
但是本程序因为牵扯到线程运行的不确定性,所以存在以下两个问题:

(1)假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程把信息的名称和上一个信息的内容联系到一起。
(2)生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没等生产者放入新的数据,又重复取出已经去过的数据。
例:

package com.shuai.ChapterSix;

class Info{
	private String name="李兴华";
	private String content="JAVA讲师";
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name=name;
	}
	
	public void setContent(String content) {
		this.content=content;
	}
	public String getContent() {
		return content;
	}
	
}

class Producer implements Runnable{

	private Info info=null;
	public Producer(Info info) {
		// TODO Auto-generated constructor stub
	    this.info=info;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		boolean flag=false;
		for(int i=0;i<50;i++){
			if(flag) {
				this.info.setName("guai");
				try {
					Thread.sleep(90);
				}catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
				this.info.setContent("JAVA讲师");
				flag=false;
			}else {
				this.info.setName("mldn");
				try {
					Thread.sleep(90);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.info.setContent("www。mldn.java.con");
				flag=true;
			}
		}
	}
	
}

class Consumer implements Runnable{
	private Info info=null;
	public Consumer(Info info) {
		// TODO Auto-generated constructor stub
		this.info=info;
		
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.info.getName()+"--->"+this.info.getContent());
		}
	}
	
}

public class ProducersAndConsumers {
	public static void main(String[] args) {
		Info info=new Info();
		Producer pro=new Producer(info);
		Consumer con=new Consumer(info);
		
		new Thread(pro).start();
		new Thread(con).start();
	}
}

结果:

在这里插入图片描述
可见上面提到的两个问题都出现了。
1、消费者将这此的信息名称和上次的信息内容联系到一起
2、生产者放了若干次数据,消费者才开始取数据,或者消费者取出的上次生产者生产的数据。

1.1、问题解决一
加入同步

package com.shuai.ChapteNine;

class Info {
	private String name = "李兴华";
	private String content = "JAVA讲师";

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public synchronized void set(String name, String context) {

		this.setName(name);
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.setContent(context);
	}

	public synchronized void get() {

		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.getName() + "--->" + this.getContent());
	}
}

class Producer implements Runnable {

	private Info info = null;
	public Producer(Info info) {
		// TODO Auto-generated constructor stub
		this.info = info;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		boolean flag = true;
		for (int i = 0; i < 20; i++) {
			if (flag) {
				flag = !flag;
				this.info.set("shuai", "java工程师");
			} else {
				flag = !flag;
				this.info.set("mldn", "www.mldn.con");
			}
		}
	}
}

class Consumer implements Runnable {

	private Info info;

	public Consumer(Info info) {
		// TODO Auto-generated constructor stub
		this.info = info;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub

		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.info.get();
		}
	}
}

public class ProducersAndConsumers {
	public static void main(String[] args) {
		Info info = new Info();
		Producer pro = new Producer(info);
		Consumer con = new Consumer(info);
		new Thread(pro).start();
                   new Thread(con).start();
	}
}

结果:从结果中可以看出,信息错乱的问题解决了但是重复读取的问题依然存在。

注意:信息错乱的问题如何解决的呢?
在Info中加入了两个同步方法,一个使存信息方法,一个是读取信息方法。因为多线程不能访问同一个类(的同一个实例)的两个同步方法,这就使得,只有当生产者线程将完整的生产信息存入Info中且生产者线程处于等待状态时(此时的线程由操作系统调度,有很大的不确定性,可能一直处于等待状态,也可能一直处于执行状态),消费者才可以读取信息。这就解决了信息混乱的问题,但是因为操作生产者线程状态的不确定性导致消费者在取数据时不能及时取出生产者生产的最新数据。而重复读取的问题就依然存在。

在这里插入图片描述

1.2、问题解决二
为了解决消费者重复读取信息的问题可以加入等待与唤醒

1.2.1、Object类对线程的支持–等待与唤醒
我们知道Object类是所有类的父类,在此类中有几种方法是对线程操作有所支持的

1、public final void wait() throws InterruptedException 线程等待
2、public final void wait(long timeout) throws InterruptedException 线程等待并指定等待的最长时间
3、public final void wait(long timeout,int nanos) throws InterruptedException 线程等待并指定等待的最长毫秒及纳秒
4、public fianl void notify() 唤醒第一个等待的线程
5、public fianl void notifyAll() 唤醒所有等待的线程

1.2.2、使用Object类对线程的支持,来解决问题

如果让生产者不重复生产,消费者不重复取走,则可以增加标志位,当标志位为true时,表示可以生产,但不能取走,此时线程执行到了消费者线程应该等待,如果标志位为false,表示可以取走,但不能生产,如果生产者线程运行,则消费者线程应该等待。

package com.shuai.ChapteNine;

class Info {
	private String name = "李兴华";
	private String content = "JAVA讲师";
	private boolean flag = true;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public synchronized void set(String name, String context) {
		if (!this.flag) {
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
		}
		this.setName(name);
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.setContent(context);
		this.flag = !this.flag;
        super.notify();
	}

	public synchronized void get() {

		if(this.flag) {
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.getName() + "--->" + this.getContent());	
		this.flag=!this.flag;
		super.notify();
	}
}

class Producer implements Runnable {

	private Info info = null;
	public Producer(Info info) {
		// TODO Auto-generated constructor stub
		this.info = info;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		boolean flag = true;
		for (int i = 0; i < 20; i++) {
			if (flag) {
				flag = !flag;
				this.info.set("shuai", "java工程师");

			} else {
				flag = !flag;
				this.info.set("mldn", "www.mldn.con");
			}
		}
	}
}

class Consumer implements Runnable {

	private Info info;
	public Consumer(Info info ) {
		// TODO Auto-generated constructor stub
		this.info = info;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.info.get();
		}
	}

}

public class ProducersAndConsumers {
	public static void main(String[] args) {
		Info info = new Info();
		Producer pro = new Producer(info);
		Consumer con = new Consumer(info);
		
		new Thread(pro).start();
		new Thread(con).start();
	}
}

结果:在这里插入图片描述
完结撒花。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值