您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
【第9章:多线程】_线程操作案例——生产者和消费者
发布时间:2020-12-21 19:58:41编辑:雪饮阅读()
一个有问题的生产者与消费者模式
class Info{
private String name;
private String content;
public void setName(String name){
this.name=name;
}
public void setContent(String content){
this.content=content;
}
public String getName(){
return this.name;
}
public String getContent(){
return this.content;
}
}
class Producer implements Runnable{
private Info info=null;
public Producer(Info info){
this.info=info;
}
public void run(){
boolean flag = false ;
for(int x=0;x<50;x++){
if(flag){
this.info.setName("kasumi"+x);
try{
Thread.sleep(90);
}
catch(InterruptedException e){
e.printStackTrace();
}
this.info.setContent("MugenTenshin"+x);
flag=false;
}
else{
this.info.setName("霞"+x);
try{
Thread.sleep(90);
}
catch(InterruptedException e){
e.printStackTrace();
}
this.info.setContent("雾幻天神流"+x);
flag=true;
}
}
}
}
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info){
this.info=info;
}
public void run(){
for(int i=0;i<50;i++){
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
}
public class Hello{
public static void main(String args[]){
Info info=new Info();
Producer producer=new Producer(info);
Consumer consumer=new Consumer(info);
new Thread(producer).start();
new Thread(consumer).start();
}
}
其实这个案例的目标是想要不断的循环修改info的name和content并且每次修改的和上次正好相反,比如上次info修改的内容是英文,则这次修改的为中文,并且无论本次修改目标是中文或者英文,但修改name后要休眠下再修改content。但是实际的实现结果却是有问题的,本来name和content应该是info的一个整体,可是由于生产者与消费者共同产生了多线程,同时进行的,所以一个写一个读,中间交叉读取就会出现了上面这个name与content不对应的结果。
Name与content不对应的解决
问题是因为name与content在线程休眠后重新唤醒后再次修改content时候可能已经与上次线程修改name的已经不在一个线程。或者读取的时候休眠之后读取的content与对应的写入content的线程也没有对应上。
那么我们只需要把写入name与content封装为一个方法并且是同步方法,同时读取name与content的也封装为一个同步方法就可以解决了。
class Info{
private String name;
private String content;
public void setName(String name){
this.name=name;
}
public void setContent(String content){
this.content=content;
}
public String getName(){
return this.name;
}
public String getContent(){
return this.content;
}
public synchronized void set(String name,String content){
this.setName(name);
try{
Thread.sleep(90);
}
catch(InterruptedException e){
e.printStackTrace();
}
this.setContent(content);
}
public synchronized String get(){
try{
Thread.sleep(90) ;
}
catch(InterruptedException e){
e.printStackTrace() ;
}
return this.getName() + " --> " + this.getContent();
}
}
class Producer implements Runnable{
private Info info=null;
public Producer(Info info){
this.info=info;
}
public void run(){
boolean flag = false ;
for(int x=0;x<50;x++){
if(flag){
this.info.set("kasumi"+x,"MugenTenshin"+x);
flag=false;
}
else{
this.info.set("霞"+x,"雾幻天神流"+x);
flag=true;
}
}
}
}
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info){
this.info=info;
}
public void run(){
for(int i=0;i<50;i++){
System.out.println(this.info.get());
}
}
}
public class Hello{
public static void main(String args[]){
Info info=new Info();
Producer producer=new Producer(info);
Consumer consumer=new Consumer(info);
new Thread(producer).start();
new Thread(consumer).start();
}
}
可以看到现在name与content读取不对应的问题得到了解决,但是仍旧有其它问题,会发现同一个info有重复读取,比如图中49号info
重复读取问题解决
class Info{
private String name;
private String content;
private boolean flag = false;
public void setName(String name){
this.name=name;
}
public void setContent(String content){
this.content=content;
}
public String getName(){
return this.name;
}
public String getContent(){
return this.content;
}
public synchronized void set(String name,String content){
if(!flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
this.setName(name);
try{
Thread.sleep(90);
}
catch(InterruptedException e){
e.printStackTrace();
}
this.setContent(content);
flag=false;
//线程唤醒
super.notify();
}
public synchronized String get(){
if(flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
try{
Thread.sleep(90) ;
}
catch(InterruptedException e){
e.printStackTrace() ;
}
flag = true ;
super.notify() ;
return this.getName() + " --> " + this.getContent();
}
}
class Producer implements Runnable{
private Info info=null;
public Producer(Info info){
this.info=info;
}
public void run(){
boolean flag = false ;
for(int x=0;x<50;x++){
if(flag){
this.info.set("kasumi"+x,"MugenTenshin"+x);
flag=false;
}
else{
this.info.set("霞"+x,"雾幻天神流"+x);
flag=true;
}
}
}
}
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info){
this.info=info;
}
public void run(){
for(int i=0;i<50;i++){
System.out.println(this.info.get());
}
}
}
public class Hello{
public static void main(String args[]){
Info info=new Info();
Producer producer=new Producer(info);
Consumer consumer=new Consumer(info);
new Thread(producer).start();
new Thread(consumer).start();
}
}
上面出现的问题其实就是生产者在还没有生产线程还没有结束之前由于消费者与生产者构造异步多线程,所以消费者一直重复读取了旧的信息。
所有类都是Object的派生类,这里自定义的Info类也不例外,Object类提供了wait方法用于等待其它沉睡线程,Object类提供了notify方法用于声明当前线程已经从沉睡中唤醒。
利用这两个方法的特性则可以让生产者在生产期间不允许消费者去读取。同样的消费者消费期间生产者也不允许生产。这就实现了完全同步。
上面封装的set方法中代码块开头的!flag与结尾的flag=false最容易让人误解,为何要多次一举,其实是因为set方法中的wait等待的结果就是get唤醒(读取结束)的结果为true,所以并没有多此一举,那么get中同样的flag也是没有多次一举,这两个是相辅相成的。
关键字词:java,线程,生产者,消费者
上一篇:【第9章:多线程】_同步与死锁
下一篇:【第9章:多线程】_线程生命周期