package Thread;
/**
* 生产者消费者过程模拟
*
* @author ZHI
*
*/
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();
}
}
// 面包类
class Bread {
private int id;
public Bread(int id) {
this.id = id;
}
}
// 面包仓库类
class SyncStack {
Bread[] bArr = new Bread[10];
private int index = 0;
public int getIndex() {
return index;
}
public synchronized void push(Bread b) {
while (index == bArr.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
bArr[index] = b;
System.out.println("生产者生产了:" + index);
index++;
}
public synchronized Bread pop() {
while (index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
System.out.println("消费者消费了:" + index);
return bArr[index];
}
}
// 生产者类
class Producer implements Runnable {
SyncStack ss;
public Producer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for (int i = 0; i < 30; i++) {
Bread b = new Bread(i);
ss.push(b);
try {
Thread.sleep(100);//每生产一个面包,休眠100
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者类
class Consumer implements Runnable {
SyncStack ss;
public Consumer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for (int i = 0; i < 30; i++) {
ss.pop();
try {
Thread.sleep(500);//每消费一个面包,休眠500
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
以上是一个典型的生产者消费者的例子。其实编程过程中本人的主要问题有以下几点:
① Stack(堆栈)的手动实现
见上述待遇面包仓库类SyncStack的实现,一般的Stack代码实现如下:
class Stack {
int[] intArr = new int[10];// Stack的容量
private int index = 0;// 元素索引
public void push(int i) {// 入栈方法
intArr[index] = i;
index++;
}
public int pop() {// 出栈方法
index--;
return intArr[index];
}
}
② Thread.sleep()方法的功能
Thread.sleep()方法表示让当前正在执行的线程休眠,但不会释放对象锁,任何代码中出现Thread.sleep()方法即表示当前执行该代码的线程进入休眠状态。注意与wait()方法的区别。
③ 为什么wait()方法要放在while循环里
如果wait()放在if中,即
public synchronized Bread pop() {
if (index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
System.out.println("消费者消费了:" + index);
return bArr[index];
}
当程序执行wait时,线程突然被打断,即跳到catch中,随后跳出while循环,然后继续执行接下来的代码,但显然这不是我们需期望的。我们希望程序在执行wait被打断时,仍然能够再次回去执行wait。所以只能把wait方法放在while循环之中。
④ wait()和notify()的用法,及其在整个代码执行过程中的流程功能。
wait()和notify()两个方法只能用在一个对象的同步方法中或取得对象锁的同步代码块中,而且对象调用wait()方法后当前锁定该对象的线程会释放锁并进入等待状态,这个等待的线程只有等到该对象再次调用notify()方法时,才会再次进入竞争对象锁的就绪状态。而对象调用notify()方法时,当前锁定该对象的线程不会释放锁,即表示该线程会继续执行。
上述代码的执行结果如下:
生产者生产了:0
消费者消费了:0
生产者生产了:0
生产者生产了:1
生产者生产了:2
生产者生产了:3
消费者消费了:3
生产者生产了:3
生产者生产了:4
生产者生产了:5
生产者生产了:6
生产者生产了:7
消费者消费了:7
生产者生产了:7
生产者生产了:8
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
生产者生产了:9
消费者消费了:9
消费者消费了:8
消费者消费了:7
消费者消费了:6
消费者消费了:5
消费者消费了:4
消费者消费了:3
消费者消费了:2
消费者消费了:1
消费者消费了:0
当main方法里两个线程(Thread(p)和Thread(c))同时启动时,即生产者线程和消费者线程同时竞争同一个面包仓库类SyncStack。
而开始时仓库里没有元素,若消费者线程首先拿到仓库类的对象锁,即执行pop()时,消费者线程就会进入等待状态,并释放对象锁,此时生产者线程即可取得仓库对象锁并执行push()方法,而执行push方法时,同时会执行notify()方法,此时之前进入等待锁的消费者线程就会进入竞争对象锁的就绪状态,但消费者线程并不会立即执行。然后生产者线程的push()方法执行完毕后,就释放对象锁。
但由于程序中生产者线程每生产一个面包后休眠的时间要比消费者线程每消费一个面包后休眠的时间短得多,故生产者线程生产面包的速度要快于消费者线程消费面包的过程,故可以看到生产者之后就会一直生产面包,直到有一个消费者线程休眠结束后消费一个面包。
当面包仓库满时,生产者线程执行push()方法时会进入等待状态,直到消费者线程执行pop()方法消费一个面包,并执行notify方法,从而通知生产者线程进入竞争锁的就绪状态,生产者线程取得锁之后立马生产一个面包后随即再次执行生产面包操作时(由于生产速度远快于消费速度),便会再次进入等待状态,如此循环,在仓库满后,每消费一个面包后立马生产一个面包。
其实从上述代码中也可以看出,SyncStack仓库类中的两个同步方法push()和pop()中各自的判断条件决定了最初的执行步骤绝对是生产者先生产第一个面包。