线程操作案例——生产者和消费者

本章目标
加深线程同步操作的理解
了解Object类中对线程的支持方法

 

实例要求
在线程操作中有一个经典的案例程序 —— 生产者和消费者问题,生产者不断生产,消费者不断取走生产者生产的产品。

 

在图中非常清楚的表示出,生产者生产出信息之后将其放到一个区域之中,之后消费者从此区域里取出数据

 

程序的问题
但是在本程序中因为牵涉到线程运行的不确定性,所以会存在以下两点问题:
——1、假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入这信息的内容,程序就切换到了消费者线程,消费者线程将把这信息的名称和上一个信息的内容联系到了一起。
——2、生产者放了若干次的数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没等到生产者放入新的数据,又重复取出已取过的数据。
下面分别来看这两个问题的产生。

 

程序的基本实现
因为现在程序中生产者不断生产的是信息,而消费者不断取出的也是信息,所以定义一个保存信息的类 —— Info.java。

public class Info {//定义信息类
	private String name = "chaoyi";//信息名称,指定默认值
	private String content = "I am Chaoyi!!!";//信息内容,指定默认值
	public String getName() {//取得信息名称
		return name;//返回信息名称
	}
	public void setName(String name) {//设置信息名称
		this.name = name;//设置 name 属性
	}
	public String getContent() {//取得信息内容
		return content;//返回信息内容
	}
	public void setContent(String content) {//设置信息内容
		this.content = content;//设置 content 属性内容
	}
}

 

生产者
public class Producer implements Runnable {//定义生产者线程
	private Info info = null;//保存 Info 引用
	public Producer(Info info){//通过构造方法设置 info 属性内容
		this.info = info;//为 info 属性初始化
	}
	public void run(){//覆写 run() 方法
		boolean flag = false;//定义标记位
		for(int i=0; i<50; i++){//循环 50 次
			if(flag){//如果为 true ,则设置第 1 个信息
				this.info.setName("chaoyi");//设置信息名称
				try{
					Thread.sleep(90);//加入延迟
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				this.info.setContent("I am Chaoyi!!!");//设置信息内容
				flag = false;//修改标记位
			}else{//如果为 false ,则设置第 2 个信息
				this.info.setName("Hello");//设置信息名称
				try{
					Thread.sleep(90);//加入延迟
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				this.info.setContent("Hello World!!!");//设置信息内容
				flag = true;//修改标记位
			}
		}
	}
}

 

消费者

public class Consumer implements Runnable {//定义消费者线程
	private Info info = null;//保存 Info 引用
	public Consumer(Info info){//通过构造方法设置 info 属性内容
		this.info = info;//为 info 属性初始化
	}
	public void run(){//覆写 run() 方法
		for(int i=0; i<50; i++){//循环 50 次
			try{
				Thread.sleep(90);//加入延迟
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			//取出信息
			System.out.println(this.info.getName() + " --> " +this.info.getContent());
		}
	}
}

 

测试程序
public class ThreadCaseDemo01 {
	public static void main(String[] args) {		
		Info i = new Info();//实例化 Info 对象
		Producer pro = new Producer(i);//实例化生产者,传递 Info 引用
		Consumer con = new Consumer(i);//实例化消费者,传递 Info 引用
		new Thread(pro).start();//启动生产者线程
		new Thread(con).start();//启动消费者线程
	}
/* 结果:
 * chaoyi --> Hello World!!!
 * chaoyi --> I am Chaoyi!!!
 * chaoyi --> Hello World!!!
 * chaoyi --> Hello World!!!
 * Hello --> I am Chaoyi!!!
 * chaoyi --> Hello World!!!
 * Hello --> Hello World!!!
 * chaoyi --> Hello World!!!
 * Hello --> I am Chaoyi!!!
 * chaoyi --> I am Chaoyi!!!
 * Hello --> I am Chaoyi!!!
 * chaoyi --> Hello World!!!
 * */
}

 

问题解决1 —— 加入同步
如果要想为操作加入同步,则可以通过定义同步方法的方式完成,即:将设置名称和姓名定义成一个同步方法完成。

 

修改Info类

public class Info {//定义信息类
	private String name = "chaoyi";//信息名称,指定默认值
	private String content = "I am Chaoyi!!!";//信息内容,指定默认值
	public synchronized void set(String name,String content){//设置信息名称及内容
		this.setName(name);//设置信息名称
		try{
			Thread.sleep(300);//加入延迟
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		this.setContent(content);//设置信息内容
	}
	public synchronized void get(){//取得信息内容
		try{
			Thread.sleep(300);//加入延迟
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		//输出信息
		System.out.println(this.getName() + " --> "+this.getContent());
	}
	public String getName() {//取得信息名称
		return name;//返回信息名称
	}
	public void setName(String name) {//设置信息名称
		this.name = name;//设置 name 属性
	}
	public String getContent() {//取得信息内容
		return content;//返回信息内容
	}
	public void setContent(String content) {//设置信息内容
		this.content = content;//设置 content 属性内容
	}
}

 

修改生产者
public class Producer implements Runnable {//定义生产者线程
	private Info info = null;//保存 Info 引用
	public Producer(Info info){//通过构造方法设置 info 属性内容
		this.info = info;//为 info 属性初始化
	}
	public void run(){//覆写 run() 方法
		boolean flag = false;//定义标记位
		for(int i=0; i<50; i++){//循环 50 次
			if(flag){//如果为 true ,则设置第 1 个信息
				this.info.set("chaoyi", "I am Chaoyi!!!");//设置信息
				flag = false;//修改标记位
			}else{//如果为 false ,则设置第 2 个信息
				this.info.set("Hello", "Hello World!!");//设置信息
				flag = true;//修改标记位
			}
		}
	}
}

 

修改消费者
public class Consumer implements Runnable {//定义消费者线程
	private Info info = null;//保存 Info 引用
	public Consumer(Info info){//通过构造方法设置 info 属性内容
		this.info = info;//为 info 属性初始化
	}
	public void run(){//覆写 run() 方法
		for(int i=0; i<50; i++){//循环 50 次
			try{
				Thread.sleep(100);//加入延迟
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			//取出信息
			this.info.get();
		}
	}
}

 

测试程序

public class ThreadCaseDemo01 {
	public static void main(String[] args) {		
		Info i = new Info();//实例化 Info 对象
		Producer pro = new Producer(i);//实例化生产者,传递 Info 引用
		Consumer con = new Consumer(i);//实例化消费者,传递 Info 引用
		new Thread(pro).start();//启动生产者线程
		new Thread(con).start();//启动消费者线程
	}
/* 结果:
 * Hello --> Hello World!!
 * chaoyi --> I am Chaoyi!!!
 * Hello --> Hello World!!
 * chaoyi --> I am Chaoyi!!!
 * Hello --> Hello World!!
 * chaoyi --> I am Chaoyi!!!
 * Hello --> Hello World!!
 * chaoyi --> I am Chaoyi!!!
 * chaoyi --> I am Chaoyi!!!
 * chaoyi --> I am Chaoyi!!!
 * */
}

 

问题的解决

 

Object类对线程的支持 —— 等待与唤醒
Object类是所有类的父类,在此类中有以下几个方法是对线程操作有所支持的。

 

notify()和notifyAll()
对于唤醒的操作有两个:notify()、notifyAll()。一般来说,所有等待的线程会按照顺序进行排列,如果现在使用了notify()方法的话,则会唤醒第一个等待的线程执行,而如果使用了notifyAll()方法,则会唤醒所有的等待线程,那个线程的优先级高,那个线程就有可能先执行。

 

问题解决2 —— 加入等待与唤醒
如果要想让生产者不重复生产,消费者不重复取走,则可以增加一个标志位,假设标志位为boolean型变量,如果标志位的内容为true,则表示可以生产,但是不能取走,如果此时线程执行到了消费者线程则应该等待,如果标志位的内容为false,则表示可以取走,但是不能生产,如果生产者线程运行,则应该等待。

 

修改Info类

public class Info {//定义信息类
	private String name = "chaoyi";//信息名称,指定默认值
	private String content = "I am Chaoyi!!!";//信息内容,指定默认值
	private boolean flag = false;
	public synchronized void set(String name,String content){//设置信息名称及内容
		if(!flag){//标志位为 false ,不可以生产
			try{
				super.wait();//等待消费者取走
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		this.setName(name);//设置信息名称
		try{
			Thread.sleep(300);//加入延迟
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		this.setContent(content);//设置信息内容
		flag = false;//修改标志位,表示可以取走
		super.notify();//唤醒等待线程
	}
	public synchronized void get(){//取得信息内容
		if(flag){//标志位为 true ,不可以取走
			try{
				super.wait();//等待生产者生产
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		try{
			Thread.sleep(300);//加入延迟
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		//输出信息
		System.out.println(this.name+" --> "+this.getContent());
		//修改标志位为 true ,表示可以生产
		flag = true;
		super.notify();//唤醒等待线程
	}
	public String getName() {//取得信息名称
		return name;//返回信息名称
	}
	public void setName(String name) {//设置信息名称
		this.name = name;//设置 name 属性
	}
	public String getContent() {//取得信息内容
		return content;//返回信息内容
	}
	public void setContent(String content) {//设置信息内容
		this.content = content;//设置 content 属性内容
	}
}

 

测试程序
public class ThreadCaseDemo01 {
	public static void main(String[] args) {		
		Info i = new Info();//实例化 Info 对象
		Producer pro = new Producer(i);//实例化生产者,传递 Info 引用
		Consumer con = new Consumer(i);//实例化消费者,传递 Info 引用
		new Thread(pro).start();//启动生产者线程
		new Thread(con).start();//启动消费者线程
	}
/* 结果:
 * chaoyi --> I am Chaoyi!!!
 * Hello --> Hello World!!
 * Hello --> Hello World!!
 * Hello --> Hello World!!
 * chaoyi --> I am Chaoyi!!!
 * Hello --> Hello World!!
 * chaoyi --> I am Chaoyi!!!
 * Hello --> Hello World!!
 * */
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值