1、在线程操作中有一个经典案例程序,即生产者和消费者问题,生产者不断生产,消费者不断取走生产者生产的产品。
但是本程序因为牵扯到线程运行的不确定性,所以存在以下两个问题:
(1)假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程把信息的名称和上一个信息的内容联系到一起。
(2)生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没等生产者放入新的数据,又重复取出已经去过的数据。
例:
package com.shuai.ChapterSix;
class Info{
private String name="李兴华";
private String content="JAVA讲师";
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public void setContent(String content) {
this.content=content;
}
public String getContent() {
return content;
}
}
class Producer implements Runnable{
private Info info=null;
public Producer(Info info) {
// TODO Auto-generated constructor stub
this.info=info;
}
@Override
public void run() {
// TODO Auto-generated method stub
boolean flag=false;
for(int i=0;i<50;i++){
if(flag) {
this.info.setName("guai");
try {
Thread.sleep(90);
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
this.info.setContent("JAVA讲师");
flag=false;
}else {
this.info.setName("mldn");
try {
Thread.sleep(90);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.info.setContent("www。mldn.java.con");
flag=true;
}
}
}
}
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info) {
// TODO Auto-generated constructor stub
this.info=info;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<50;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.info.getName()+"--->"+this.info.getContent());
}
}
}
public class ProducersAndConsumers {
public static void main(String[] args) {
Info info=new Info();
Producer pro=new Producer(info);
Consumer con=new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
结果:
可见上面提到的两个问题都出现了。
1、消费者将这此的信息名称和上次的信息内容联系到一起
2、生产者放了若干次数据,消费者才开始取数据,或者消费者取出的上次生产者生产的数据。
1.1、问题解决一
加入同步
package com.shuai.ChapteNine;
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 context) {
this.setName(name);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setContent(context);
}
public synchronized void get() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + "--->" + this.getContent());
}
}
class Producer implements Runnable {
private Info info = null;
public Producer(Info info) {
// TODO Auto-generated constructor stub
this.info = info;
}
@Override
public void run() {
// TODO Auto-generated method stub
boolean flag = true;
for (int i = 0; i < 20; i++) {
if (flag) {
flag = !flag;
this.info.set("shuai", "java工程师");
} else {
flag = !flag;
this.info.set("mldn", "www.mldn.con");
}
}
}
}
class Consumer implements Runnable {
private Info info;
public Consumer(Info info) {
// TODO Auto-generated constructor stub
this.info = info;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.info.get();
}
}
}
public class ProducersAndConsumers {
public static void main(String[] args) {
Info info = new Info();
Producer pro = new Producer(info);
Consumer con = new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
结果:从结果中可以看出,信息错乱的问题解决了但是重复读取的问题依然存在。
注意:信息错乱的问题如何解决的呢?
在Info中加入了两个同步方法,一个使存信息方法,一个是读取信息方法。因为多线程不能访问同一个类(的同一个实例)的两个同步方法,这就使得,只有当生产者线程将完整的生产信息存入Info中且生产者线程处于等待状态时(此时的线程由操作系统调度,有很大的不确定性,可能一直处于等待状态,也可能一直处于执行状态),消费者才可以读取信息。这就解决了信息混乱的问题,但是因为操作生产者线程状态的不确定性导致消费者在取数据时不能及时取出生产者生产的最新数据。而重复读取的问题就依然存在。
1.2、问题解决二
为了解决消费者重复读取信息的问题可以加入等待与唤醒
1.2.1、Object类对线程的支持–等待与唤醒
我们知道Object类是所有类的父类,在此类中有几种方法是对线程操作有所支持的
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 fianl void notify() 唤醒第一个等待的线程
5、public fianl void notifyAll() 唤醒所有等待的线程
1.2.2、使用Object类对线程的支持,来解决问题
如果让生产者不重复生产,消费者不重复取走,则可以增加标志位,当标志位为true时,表示可以生产,但不能取走,此时线程执行到了消费者线程应该等待,如果标志位为false,表示可以取走,但不能生产,如果生产者线程运行,则消费者线程应该等待。
package com.shuai.ChapteNine;
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 context) {
if (!this.flag) {
try {
super.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setContent(context);
this.flag = !this.flag;
super.notify();
}
public synchronized void get() {
if(this.flag) {
try {
super.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + "--->" + this.getContent());
this.flag=!this.flag;
super.notify();
}
}
class Producer implements Runnable {
private Info info = null;
public Producer(Info info) {
// TODO Auto-generated constructor stub
this.info = info;
}
@Override
public void run() {
// TODO Auto-generated method stub
boolean flag = true;
for (int i = 0; i < 20; i++) {
if (flag) {
flag = !flag;
this.info.set("shuai", "java工程师");
} else {
flag = !flag;
this.info.set("mldn", "www.mldn.con");
}
}
}
}
class Consumer implements Runnable {
private Info info;
public Consumer(Info info ) {
// TODO Auto-generated constructor stub
this.info = info;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.info.get();
}
}
}
public class ProducersAndConsumers {
public static void main(String[] args) {
Info info = new Info();
Producer pro = new Producer(info);
Consumer con = new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
结果:
完结撒花。