本章目标
加深线程同步操作的理解
了解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!! * */ }
相关推荐
线程这一知识点一直是困扰大家学习java的一个难点。此处笔者提供了其两个经典案例——生产者与消费者、同步两个操作者取钱问题的代码。供大家参考
该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗...
030905_【第9章:多线程】_线程操作案例——生产者和消费者笔记.pdf 030906_【第9章:多线程】_线程生命周期笔记.pdf 031001_【第10章:泛型】_泛型入门笔记.pdf 031002_【第10章:泛型】_通配符笔记.pdf 031003_...
2.2 设计要求 (1)每个生产者和消费者对有界缓冲区进行操作后,实时显示有界缓冲区的全部内容 、当前指针位置和生产者/消费者的标识符。 (2)生产者和消费者各有两个以上。 (3)多个生产者或多个消费者之间须有共享对...
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定...该问题也能被推广到多个生产者和消费者的情形。
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer ...该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。
7.3.1 生产者和消费者的类 7.3.2 储存库的类 7.4 知识点扩展——线程的通信知识 7.4.1 线程通信的基本知识 7.4.2 线程通信的具体实例 7.5 小结 第8章 关机工具(Timer类+系统命令) 8.1 关机工具原理 8.1.1 项目结构...
实验2.3 Linux多线程使用实例——生产者-消费者协议24 2.4 进程创建以及进程间通信25 2.4.1 进程概述25 2.4.2 进程的相关函数25 2.4.3 信号概述27 2.4.4 信号的相关函数27 2.4.5 管道概述27 2.4.6 ...
实验2.3 Linux多线程使用实例——生产者-消费者协议24 2.4 进程创建以及进程间通信25 2.4.1 进程概述25 2.4.2 进程的相关函数25 2.4.3 信号概述27 2.4.4 信号的相关函数27 2.4.5 管道概述27 2.4.6 管道的相关...
实验2.3 Linux多线程使用实例——生产者-消费者协议24 2.4 进程创建以及进程间通信25 2.4.1 进程概述25 2.4.2 进程的相关函数25 2.4.3 信号概述27 2.4.4 信号的相关函数27 2.4.5 管道概述27 2.4.6 管道的相关...
实验2.3 Linux多线程使用实例——生产者-消费者协议24 2.4 进程创建以及进程间通信25 2.4.1 进程概述25 2.4.2 进程的相关函数25 2.4.3 信号概述27 2.4.4 信号的相关函数27 2.4.5 管道概述27 2.4.6 管道的相关...
16.4.5 “生产者-消费者”案例的实际运行 365 16.4.6 notify方法的使用 366 16.4.7 同步的语句块 367 16.4.8 线程的死锁 369 16.4.9 防止错误的使用wait、notify、notifyAll方法 371 16.5 获取当前正在...