现在我们要写一个程序,来模拟做馒头和吃馒头,一个不断的往篮子里扔做熟的馒头,一个不断的在那吃。模型如下:
现在要求是,写一个程序来模拟这个过程,面向对象,怎么写呢?
我们要写考虑里面有哪些类呢?我们分析名字就行了
首先我们先把主要的类和main方法写一下:
public class ProducerConsumer {
public static void main(String[] args) {
}
}
然后写一下馒头类:
class WoTou {
int id;
WoTou(int id) {
this.id = id;
}
public String toString() {
return "WoTou : " + id;
}
}
给他一个id,可以更好地看清哪个馒头做出来了,那个馒头被吃了。
接下来是篮子类。我们想一下,我们先做的馒头是放在底层的是吧,别人要吃都是从上面开始拿的,先进后出
,栈。
class SyncStack {
int index = 0;//装到第几个了
WoTou[] arrWT = new WoTou[6];//这个篮子能装6个馒头
//装馒头的方法
public synchronized void push(WoTou wt) {
arrWT[index] = wt;
index ++;//放了馒头,肯定要占一个位置的
}
要是上面程序不加锁会怎样呢?
当你在一个位置装了馒头之后,有另外一个线程,放馒头的人也好,吃馒头的人也好,都是一个线程。你往里扔馒头的时候,有一个人已经扔进去了,可是他被打断了,index还没来得及往上加
,下一个人又往里扔,会产生什么问题啊,会把原来的馒头给覆盖掉。index还是0。怎么解决呢?加个锁就好了!
还有一个问题就是,如果篮子满了怎么办呢?我们是不是得休息一下啊。所以要进行线程控制,用wait方法
:
public synchronized void push(WoTou wt) {
while(index == arrWT.length) {
try {
this.wait();//指的是SyncStack,让生产者休息
} catch (InterruptedException e) {
e.printStackTrace();
}
}
arrWT[index] = wt;
index ++;
}
wait
的解释是,当前的,正在我这个对象访问的线程。
一个线程访问push()方法的时候,他已经拿到这个对象的锁了,拿到对象这个锁的线程,在执行的过程之中,他遇见一个事件必须阻塞住,必须停止。什么事件,已经满了,你不能往里填了。必须等清洁工把它清走,才可以继续。没清走之前,你只能等着
。
这里我们说一下wait和sleep的区别:
上面代码是不行的,为什么不行呢,因为这个东西全等在那了,第一个人生产满了,他就在那等住了,等住了之后,另外一个人就算消费空了,篮子里边没有馒头了,可是他依然运行不了。为什么呀?因为
没有人叫醒他
。
wait时别的线程可以访问锁定对象。调用wait方法的时候必须锁定该对象。
sleep时别的线程也不可以访问锁定对象
wait和sleep不一样。sleep过一段时间他会自己醒过来,可是wait不行。wait一旦wait过去,对不起,死过去了。并且,wait一旦wait过去了,这个对象的锁不在归我所有,只有醒过来的时候才会再去找这把锁
。这是wait和sleep之间巨大的区别。wait的时候,锁不在归我所有。sleep的时候,睡着了也得抱着那把锁。
wait完之后我们要干嘛呢?我们要做一件事,就是叫消费者赶快消费是吧,这样篮子才会有空位置。这时我们就要用notify方法了。wait方法和notify方法是一一对应的。
所以我们就得在代码中加上this.notify()。当有多个线程就得用this.notiyAll()
notify的含义:叫醒一个正在wait在我这个对象上的线程。this.notify(),谁现在正在我这个对象等待着,我就叫醒谁,让它继续执行
那我们既然有放馒头的方法,肯定也有拿馒头的方法是吧:
public synchronized WoTou pop() {
index--;//拿了馒头,便把位置数-1
return arrWT[index];
}
pop方法也一样,当篮子里的馒头空了之后,就必须等待着
public synchronized WoTou pop() {
while(index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
return arrWT[index];
}
接下来我们还得有生产者这个类吧,不然馒头哪里来?他是一个线程,因为好多人在一起做馒头。
class Producer implements Runnable {
SyncStack ss = null;//生产者得知道往哪个框生产吧,所以得引用一个对象。
Producer(SyncStack ss) {
this.ss = ss;
}
有了线程,就得有启动线程的方法
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = new WoTou(i);
ss.push(wt);
System.out.println("生产了:" + wt);
try {
Thread.sleep((int)(Math.random() * 1000));
//为了方便观察,每生产一个,便睡眠一会
//Math.random()是一个double类型,这里强制转换为int类型
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
有生产者的类,自然也就有消费者的类:
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
}
消费者这个线程自然也有启动的方法:
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = ss.pop();
System.out.println("消费了: " + wt);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
接下来我们就得模拟一下线程了。首先得造一个框出来装馒头吧。
public class ProducerConsumer {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(c).start();
}
}
这里只模拟了一个生产者和一个消费者。
下面来看看完整代码:
package Thread;
public class ProducerConsumer {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(p).start();
new Thread(c).start();
new Thread(c).start();
}
}
class WoTou {
int id;
WoTou(int id) {
this.id = id;
}
public String toString() {
return "WoTou : " + id;
}
}
class SyncStack {
int index = 0;
WoTou[] arrWT = new WoTou[6];
public synchronized void push(WoTou wt) {
while(index == arrWT.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
arrWT[index] = wt;
index ++;
}
public synchronized WoTou pop() {
while(index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
return arrWT[index];
}
}
class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = new WoTou(i);
ss.push(wt);
System.out.println("生产了:" + wt);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = ss.pop();
System.out.println("消费了: " + wt);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
多线程(三)也写到这啦,这个章节比较简单,就说了一下生产者和消费者的问题,也说了wait和sleep的区别,这个是重点!