在线程操作中有一个经典的案例程序——生产者与消费者问题,即生产者不断生产,消费者不断取走生产者所生产的产品。
温馨提示:如果不想看分析过程,可以直接看目录的3.完整代码。
1. 基本实现
1)Info.java(信息类)
因为现在程序中生产者不断生产的是信息,而消费者取出的也是信息,所以定义一个保存信息的Info.java类。
public 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;
}
}
2)Producer.java(生产者)
生产者和消费者现在要操作同一空间的内容,属于两个不同的线程,我们均采用实现接口Runnable来设置线程。
public class Producer implements Runnable{
private Info info; //保存Info引用,生产信息
public Producer(Info info){ //通过构造方法设置info内容
this.info=info;
}
@Override
public void run() { //覆写run()方法,来生产信息(这里设置50条交替信息)
boolean flag=true; //通过flag实现两组信息的交替设置
for(int i=0;i<50;i++){
if(flag){
this.info.setName("李兴华");
try {
Thread.sleep(300); //添加睡眠,使得效果更加明显
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.info.setContent("JAVA讲师");
flag=false;
}else{
this.info.setName("mldn");
try {
Thread.sleep(300); //添加睡眠,使得效果更加明显
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.info.setContent("www.mldnjava.cn");
flag=true;
}
}
}
}
3) Consumer.java(消费者)
可以直接理解成将info的内容拿出来
public class Consumer implements Runnable{
private Info info;
public Consumer(Info info){
this.info=info;
}
@Override
public void run() { //覆写run()方法,取出info信息
for(int i=0;i<50;i++){ //循环取得50次信息(与生产者生产保持一致)
try {
Thread.sleep(300); //添加休眠,使得效果更加明显
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.info.getName()+"-->"+this.info.getContent());
}
}
}
4)ThreadCaseDemo01.java(主方法)
public class ThreadCaseDemo01 {
public static void main(String args[]){
Info i=new Info(); //实例化Info对象
Producer pro=new Producer(i);
Consumer con =new Consumer(i);
new Thread(pro).start();
new Thread(con).start();
}
}
运行结果(部分):
2. 出现的问题
1)信息的题目与内容不匹配-->假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入这信息的内容时,程序就切换到了消费者线程,消费者线程就会把这次信息的名称和上一个信息的内容匹配在一起。
2)重复存取问题-->生产者放入了若干次数据,消费者才开始取数据,或者是消费者取完一个数据后,还没有等到消费放入新的数据,又重复取已经取过的数据。
2.1 问题解决1——加入同步
为了让信息的题目与内容相匹配,我们在存取数据时,给姓名和内容添加同步。为了方便,我们直接在Info类添加自定义方法set()和get()来存取数据。
修改如下:
1)Info.java(信息类)
public 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 content){
this.name=name;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.content=content;
}
public synchronized void get(){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName()+"-->"+this.getContent());
}
}
然后生产者类和消费者类也应该进行相应修改
2)Producer.java(生产者)
public class Producer implements Runnable{
private Info info; //保存Info引用,生产信息
public Producer(Info info){ //通过构造方法设置info内容
this.info=info;
}
@Override
public void run() { //覆写run()方法,来生产信息(这里设置50条交替信息)
boolean flag=true; //通过flag实现两组信息的交替设置
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师");
flag=false;
}else{
this.info.set("mldn","wwww.mldnjava.cn");
flag=true;
}
}
}
}
3) Consumer.java(消费者)
public class Consumer implements Runnable{
private Info info;
public Consumer(Info info){
this.info=info;
}
@Override
public void run() { //覆写run()方法,取出info信息
for(int i=0;i<50;i++){ //循环取得50次信息(与生产者生产保持一致)
this.info.get();
}
}
}
结果:
从结果可以看出问题1已经解决,然后我们来解决问题2.
2.2 问题解决2——加入等待与唤醒
如果想让生产者不重复生产,消费者不充分取走,则可以添加一个标志位flag(boolean型)。如果flag为true,表示可以生产,但是不能取走,如果此线程执行到了消费者线程,则应该等待;
如果flag为false,表示可以取走,但是不能生产,如果此线程执行到了生产者线程,则应该等待。
(也就是实现生产一个取一个)
在这里线程的等待和唤醒我们用Object类对线程的一些方法:
方法 | 类型 | 描述 |
public final void wait() throws InterruptedException | 普通 | 线程等待 |
public final void notify() | 普通 | 唤醒第一个等待的线程 |
public final void notifyAll() | 普通 | 唤醒全部等待线程 |
这里只需要修改Info类即可:
public 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 content){ //用于生产者
if(!flag){ //flag为false,不能生产,需要等待消费者消费
try {
super.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
this.name=name; //flag为true的情况,需要生产者生产
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.content=content;
flag=false; //修改标志位
super.notify(); //当生产者产生信息后,唤醒消费者进程
}
public synchronized void get(){
if(flag){ //当flag为true,该生产者生产,消费者需等待
try {
super.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName()+"-->"+this.getContent());
flag=true;
super.notify(); //当消费者取出信息后,唤醒生产者进程
}
}
结果:(部分)
完美解决
3. 完整代码
1)Info.java(信息类)
public 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 content){ //用于生产者
if(!flag){ //flag为false,不能生产,需要等待消费者消费
try {
super.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
this.name=name; //flag为true的情况,需要生产者生产
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.content=content;
flag=false; //修改标志位
super.notify(); //当生产者产生信息后,唤醒消费者进程
}
public synchronized void get(){
if(flag){ //当flag为true,该生产者生产,消费者需等待
try {
super.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName()+"-->"+this.getContent());
flag=true;
super.notify(); //当消费者取出信息后,唤醒生产者进程
}
}
2)Producer.java(生产者)
public class Producer implements Runnable{
private Info info; //保存Info引用,生产信息
public Producer(Info info){ //通过构造方法设置info内容
this.info=info;
}
@Override
public void run() { //覆写run()方法,来生产信息(这里设置50条交替信息)
boolean flag=true; //通过flag实现两组信息的交替设置
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师");
flag=false;
}else{
this.info.set("mldn","wwww.mldnjava.cn");
flag=true;
}
}
}
}
3) Consumer.java(消费者)
public class Consumer implements Runnable{
private Info info;
public Consumer(Info info){
this.info=info;
}
@Override
public void run() { //覆写run()方法,取出info信息
for(int i=0;i<50;i++){ //循环取得50次信息(与生产者生产保持一致)
this.info.get();
}
}
}
4.)ThreadCaseDemo01.java(主方法)
public class ThreadCaseDemo01 {
public static void main(String args[]){
Info i=new Info(); //实例化Info对象
Producer pro=new Producer(i);
Consumer con =new Consumer(i);
new Thread(pro).start();
new Thread(con).start();
}
}
结果(部分):
4. 总结
此代码只是简单实现了生产者与消费者每次先生产后消费的情况,利用同步和Object提供线程的等待与唤醒实现该功能,但是没有提供排队序列这个功能,存在一定不足。