原文:
The producer/Consumer Example
http://life.csu.edu.au/java-tut/essential/threads/synchronization.html
在这个例子中,生产者产生整数0-9存储到Cubblyhole。为了使同步问题更有趣,生产者的生产周期为0-100ms.
Cubblehole.java
public class CubbyHole {
private int contents;
private boolean available = false;
public synchronized int get(int who) {
while (!available) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
available = false;
System.out.format("Consumer %d got: %d%n", who, contents);
notifyAll();
return contents;
}
public synchronized void put(int who, int value) {
while (available) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
contents = value;
available = true;
System.out.format("Producer %d put: %d%n", who, contents);
notifyAll();
}
}
生产者 Producer.java
public class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(number, i);
try {
sleep((int) (Math.random() * 100));
} catch (InterruptedException e) {
}
}
}
}
当CubbyHole可以消费时,消费者消费CubbyHole中的整数。
消费者 Consumer.java
public class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get(number);
}
}
}
生产者和消费者共享CubbyHole对象的数据。理想上消费者一个个取得生产者生产的值,但是无论怎么样努力都不能保证这种正确数据。假定这种情况,这两个线程都没有同步;我们讨论可能会发生的潜在的错误。
第一个是,生产者的速度比消费者快,连续产生两个数字,使消费者没有取到第一个数字。这种情况下,消费者丢失了一个数字。如下图。
Consumer misses number.
另一个是,消费者的速度比生产者快,两次消费了同一个值。如下图。
Consumer gets the same number twice.
不管怎么样,这个结果是错误的,是因为消费者应该得到每一个生产者生产的数。这种情况称为竞态条件。竞态是两个或两个以上线程或者进程读或写同一个数据,处理结果依赖于线程执行时间的安排。竞态条件会产生不可预知的结果和细小的程序错误。竞态条件在这个实例中通过同步生产者存储数值到CubbyHole和消费者从CubbyHole取得数值。这种同步行为必须从两个方面同步。
第一,这两个线程不能同时使用CubbyHole。线程可能通过锁一个对象来阻止另一个线程访问。当然一个线程锁了一个对象,另一个线程尝试调用同一个对象的同步方法。第二个线程将会被阻塞直接第一个线程解锁。
第二,这两个线程必须有简单的沟通。沟通的方法是,生产者必须有方法指示消费者,产品已经生产好,等待消费,消费也必须有方法指示生产者,产品已经消费完。java对象已经提供一些方法--wait, notify, and notifyAll -- 帮助线程以特定的条件等待和通知其它线程条件已经改变。