上节,我们谈到了生产者/消费者模型,这里,我不得不把我的一次实验结果粘贴出来,本来第一次运行的挺好的,可是第二次竟然出现了如下结果:
Producer:p produced:1
Consumer:c consumed:1
Consumer:c consumed:2
Producer:p produced:2
Producer:p produced:3
Consumer:c consumed:3
Producer:p produced:4
Consumer:c consumed:4
Producer:p produced:5
Consumer:c consumed:5
奇怪,怎么第三行是消费,第四行才是生产者,我开始是怀疑我手一哆嗦点错了,可是,在经过数次运行之后,仍然出现了消费在前,生产在后......
嘿嘿,其实这是假象,我们看到的并不是先消费再生产,其实确实是先生产再消费,只是控制台先打印出了消费语句再打印出了生产语句.为什么呢?这是因为生产者线程生产数据(put())之后未打印生产语句之前突然由于某种原因(如时间分片完毕)发生中断,导致没及时打印生产语句,这时,消费者夺得了cpu资源,取走数据并打印了消费语句,随后当cpu重新处理生产者线程时,回到刚才生产者线程的中断现场,执行完刚没执行的生产语句.这就造成了先消费后生产的假象.可以想象,这样的一个视觉性漏洞,是会给实际应用带来重大损失的.
那么,如何改变这一弊端呢?第一种思路就是我们可以将打印语句写在同步方法中,即把生产语句写在put()中,消费语句写在get()中,这样,如果线程进入到了put()中,即使这时突然中断,生产者线程也不会交出对象锁,消费者线程也不会执行get()了,更不会在生产语句之前打印get()中的消费语句了.第二种思路就是我们不让生产方法(put())和消费方法(get())单独同步,而是让生产方法和生产语句一起和消费方法和消费语句一起同步,互相排斥.
上述方法在解决某些实际问题时需要三思后行,站在理解思想挖掘大脑潜力的基础上选取最佳方法,需要我们灵活运用.
这里我采取第二种方法.
我们先定义要处理的资源(Box):
public class Box {
private int value;// 要进行读写的值
private boolean available = false;// 开关变量,true即表示value已生产,false即表示value已消费
public int get() {
while (available == false) {// Box无数据
try {
wait();// 交出对象锁,等待生产者写入数据
} catch (InterruptedException e) {
e.printStackTrace();
}
}
available = false;// 开关变量取反,示意消费者可以取走数据
notifyAll();// 唤醒线程池中所有阻塞线程
return value;
}
public void put(int value) {
while (available == true) {// Box有数据
try {
wait();// 交出对象锁,等待消费者取走数据
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.value = value;
available = true;// 开关变量取反,示意生产者可以生产数据
notifyAll();// 唤醒线程池中所有阻塞线程
}
}
线程--生产者:
public class Producer extends Thread {
private Box box;// 要处理的资源
private String name;// 生产者名称
// 构造器
public Producer(Box box, String name) {
this.box = box;
this.name = name;
}
public void run() {
synchronized (box) {
for (int i = 1; i < 6; i++) {
box.put(i);// 写入数据i(即给box中的value设定初值)
System.out.println("Producer:" + name + " " + "produced:" + i);// 打印
try {
sleep((int) (Math.random() * 100));// 强制睡眠,用以等待消费者取走数据
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程--消费者:
public class Consumer extends Thread {
private Box box;// 要处理的资源
private String name;// 消费者名称
// 构造器
public Consumer(Box box, String name) {
this.box = box;
this.name = name;
}
public void run() {
synchronized (box) {
int value = 0;
for (int i = 1; i < 6; i++) {
value = box.get();// 取走数据(即获取box的value值)
System.out.println("Consumer:" + name + " " + "consumed:"
+ value);
try {
sleep((int) (Math.random() * 100));// 强制睡眠,用以等待生产者生产数据
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试类:
package produceConsumer;
public class ProducerConsumerTest {
public static void main(String[] args) {
Box box = new Box();
new Producer(box, "p").start();
new Consumer(box, "c").start();
}
}
运行结果:
Producer:p produced:1
Consumer:c consumed:1
Producer:p produced:2
Consumer:c consumed:2
Producer:p produced:3
Consumer:c consumed:3
Producer:p produced:4
Consumer:c consumed:4
Producer:p produced:5
Consumer:c consumed:5
读者朋友可以尝试着,采用继承Runnable的方式去实现java多线程入门一中的生产者/消费者模型.
下接:java多线程入门三
http://172672421-qq-com.iteye.com/admin/blogs/1129791