Java——线程——生产者——消费者问题

来源:互联网 发布:mac appstore 更新不 编辑:程序博客网 时间:2024/06/16 02:34

今天复习Java线程基础时遇到一道题,感觉和线程还有面向对象基础有关,给大家列一下,记录一下感受。

题目,目前有一个篮子,里面有馒头,有买家买这个馒头,也有生产的人生产这个馒头,那么如何规划这个程序呢?

首先处于面向对象角度考虑,我们考虑有哪些类呢,有馒头、篮子、生产者、消费者这四个类。类和类之间什么关系呢?

馒头里面有自己每个馒头的id,篮子呢有把馒头放进篮子的方法,有把馒头拿出的方法,生产者要调用把馒头放进篮子方法,消费者要调用把馒头拿出这个方法。

大概先想到这些,毕竟程序是一点一点写,后面功能再加,当然生产和消费是同步的,所以应该使用线程。

首先定义馒头类

class WoTou{
int id;
WoTou(int id)//构造方法中要传入参数
{
this.id=id;
}
public String toString()
{
return "WoTou"+id;

}
}

这个toString方法是干什么用的呢?

toString
public String toString()返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂。建议所有子类都重写此方法。
Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:

getClass().getName() + '@' + Integer.toHexString(hashCode())

说白了我们这么定义可以返回WoTou的id

接下来定义篮子这个类

class SyncStack{
int index=0;
WoTou arrWouTou[]=new WoTou[6];
public synchronized void push(WoTou wt)//指定参数返回值类型
{
while(index==arrWouTou.length)
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notifyAll();
arrWouTou[index]=wt;
index++;
}
public synchronized WoTou pop()//定义什么类型就返回什么类型
{
while(index==0)
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notifyAll();
index--;
return arrWouTou[index];
}
}

我们首选定义一个下标来判断篮子里面的馒头有几个,先把馒头创建一个数组,篮子里面最多可以放七个馒头,

定义生产馒头这个方法:synchronized 代表线程同步,此方法内程序不执行完不会解锁。

首先判断传入馒头这个对象的长度与篮子里面有馒头的长度是否相等,如果相等那么让生产者歇一会 用wait方法 等待一下。

1.wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)

最后生产多少往篮子里放多少。

而拿馒头这个方法刚好相反,

拿出馒头如果篮子里面的馒头为0我们也等一会 等馒头生产了 再拿,每次篮子里面的馒头都少一个。一开始考虑用if后来while

为什么不用if呢 因为if代表如果这么做那么怎么做,而while代表执行以后还会这么执行一次在循环中判断,效果更好。

class Producer implements Runnable{
SyncStack ss=null;//只需要对框负责不需要对馒头负责, 因为框里面已经有馒头类了可以调用
public Producer(SyncStack ss) {
this.ss=ss;//对SyncStack 对象进行初始化,通过构造方法自动将其初始化
}
//不在方法体中是不能调用对象.方法的


public void run() {
for(int i=0;i<20;i++)
{    WoTou wt=new WoTou(i);//把id放进wotou类中
ss.push(wt);//把wt对象传入篮子中
System.out.println("生产了"+wt+"个");

try {
Thread.sleep((int)(Math.random() * 200));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

实现线程的两种方式中使用第二种接口方式,接口实现更灵活,Java只支持单继承,但是可以实现多个接口。

定义生产20个馒头循环,每次把馒头放入馒头对象中,调用放篮子中方法,当然要考虑构造方法中初始化篮子对象,这样直接可以调用该方法。

构造方法一般都是初始化对象用的。

class Consumer implements Runnable{
SyncStack ss=null;//只需要对框负责不需要对馒头负责, 因为框里面已经有馒头类了可以调用
public Consumer(SyncStack ss) {
this.ss=ss;//对SyncStack 对象进行初始化,通过构造方法自动将其初始化
}
//不在方法体中是不能调用对象.方法的


public void run() {
for(int i=0;i<20;i++)
{    WoTou wt=ss.pop();//把id放进wotou类中


System.out.println("消费了"+wt+"个");

try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

消费者消费了馒头每次都拿出馒头,返回馒头的id tostring方法中。

生产者生成馒头要比消费者消费快,所以线程睡眠时间短。


System.out.println("生产了"+wt+"个");

打印的wt实际打印的是wt的tostring 方法中的返回值。

SyncStack ss=new SyncStack();
      Producer p=new Producer(ss);
      Consumer c=new Consumer(ss);
new Thread(p).start();
// new Thread(p).start();
// new Thread(p).start();
// Thread.sleep(1);
new Thread(c).start();

最后创建对象在main方法中执行。

虽然感觉程序不难,但是要考虑的点比较多,也算是应用面向对象角度考虑线程执行问题,程序是一点点改出来的而不是一下子敲出来的。