欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多线程(四) —— 生产者和消费者

程序员文章站 2024-03-24 13:29:52
...

目录

 

加深线程同步操作的理解

了解Object类中对线程的支持方法


加深线程同步操作的理解

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

多线程(四) —— 生产者和消费者第一种信息:

“张冬晖”、“Java讲师”

第二种信息:

“dong”、“com.dong1990”

 

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

程序的问题:

但是在本程序中因为牵涉到线程运行的不确定性,所以会存在以下两点问题:

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

下面分别来看这两个问题的产生。

程序的基本实现

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

class Info { 					// 定义信息类
	private String name = "张冬晖"; 		// 信息名称,指定默认值
	private String content = "JAVA讲师"; 		// 信息内容,指定默认值
	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属性内容
	}
}

                                                                                生产者

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,则设置第一个信息
				this.info.setName("张冬晖");	// 设置信息名称
				Thread.sleep(90); 		// 加入延迟,处理异常		
				this.info.setContent("JAVA讲师"); // 设置信息内容
				flag = false;		// 修改标记位
			} else {				// 如果为false,则设置第二个信息
				this.info.setName("dong");	// 设置信息名称
				Thread.sleep(90); 		// 加入延迟,处理异常
				this.info.setContent("com.dong1990"); // 设置信息内容
				flag = true; 			// 修改标记位
			}
		}
	}
}

                                                                                          消费者

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();
			}
			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(); 			// 启动消费者线程
	}
}

问题解决1 —— 加入同步

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

                                                                                   修改Info类

class Info { 						// 定义信息类
	private String name = "张冬晖"; 				// 信息名称,指定默认值
	private String content = "JAVA讲师"; 			// 信息内容,指定默认值
	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()); 	// 输出信息
	}
	// setter及getter方法
}

                                                                                         修改生产者

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,则设置第一个信息
				this.info.set("张冬晖", "JAVA讲师");	// 设置信息
				flag = false; 		// 修改标记位
			} else {				// 如果为false,则设置第二个信息
				this.info.set("dong", "com.dong1990"); // 设置信息
				flag = true; 		// 修改标记位
			}
		}
	}
}

                                                                                     修改消费者

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(); 			// 取出信息
		}
	}
}

问题的解决

多线程(四) —— 生产者和消费者

了解Object类中对线程的支持方法

                                                          —— 等待与唤醒

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

No.

方法

类型

描述

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 final void notify()

普通

唤醒第一个等待的线程

5

public final void notifyAll()

普通

唤醒全部等待的线程

notify()和notifyAll()

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

多线程(四) —— 生产者和消费者 多线程(四) —— 生产者和消费者

问题解决2 —— 加入等待与唤醒

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

多线程(四) —— 生产者和消费者 多线程(四) —— 生产者和消费者

                                                                        修改Info类

class Info { 					// 定义信息类
	private String name = "张冬晖"; 			// 信息名称,指定默认值
	private String content = "JAVA讲师"; 		// 信息内容,指定默认值
	private boolean flag = false ;			// 设置标志位
	public synchronized void set(String name, String content) { 	// 设置信息名称及内容
		if(!flag){				// 标志位为false,不可以生产
			super.wait() ;			// 等待消费者取走,处理异常		}
		this.setName(name); 			// 设置信息名称
		Thread.sleep(300); 				// 加入延迟,处理异常
		this.setContent(content) ;			// 设置信息内容
		flag = false ;				// 修改标志位,表示可以取走
		super.notify() ;				// 唤醒等待线程
	}

	public synchronized void get() { 			// 取得信息内容
		if(flag){					// 标志位为true,不可以取走
			super.wait() ;			// 等待生产者生产,处理异常
		}
		Thread.sleep(300); 				// 加入延迟,处理异常
		System.out.println(this.getName() 
				+ " --> " + this.getContent()); // 输出信息
		flag = true ;				// 修改标志位为true,表示可以生产
		super.notify() ;				// 唤醒等待线程
	}
	// setter()及getter()方法
}