1. 前言
随着摩尔定律的失效,Amdahl定律成为了多核计算机性能发展的指导。对于现在的java程序员们来说,并发编程越来越重要和习以为常。很惭愧和恐慌的是我对java的并发编程一直是只知道概念,入门都不算。最近工作需要,开始认真学习java并发编程。先找了一本简单的电子书《Java7并发编程实战手册》开始看。刚刚看到简单的生产者消费者问题,在书中给出的代码中,有一点不理解:为什么wait()语句要放在while循环之内?经过网上搜索以及翻看《effective java》第二版。终于明白了一些。特此记录下来。
2. 生产者消费者代码
生产者消费者代码如下:
数据存储类:EventStorage:(get和set标记为同步方法,并使用了wait和notify机制)
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* This class implements an Event storage. Producers will storage
* events in it and Consumers will process them. An event will
* be a java.util.Date object
*
*/
public class EventStorage {
/**
* Maximum size of the storage
*/
private int maxSize;
/**
* Storage of events
*/
private List<Date> storage;
/**
* Constructor of the class. Initializes the attributes.
*/
public EventStorage(){
maxSize=10;
storage=new LinkedList<>();
}
/**
* This method creates and storage an event.
*/
public synchronized void set(){
while (storage.size()>=maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.printf("Set: %d\n",storage.size());
notify();
}
/**
* This method delete the first event of the storage.
*/
public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll());
notify();
}
}
生产者类:Producer:(调用EventStorage类中的set方法存入数据)
/**
* This class implements a producer of events.
*
*/
public class Producer implements Runnable {
/**
* Store to work with
*/
private EventStorage storage;
/**
* Constructor of the class. Initialize the storage.
* @param storage The store to work with
*/
public Producer(EventStorage storage){
this.storage=storage;
}
/**
* Core method of the producer. Generates 100 events.
*/
@Override
public void run() {
for (int i=0; i<100; i++){
storage.set();
}
}
}
消费者类:Consumer:(调用EventStorage类中的get方法取出数据)
/**
* This class implements a consumer of events.
*
*/
public class Consumer implements Runnable {
/**
* Store to work with
*/
private EventStorage storage;
/**
* Constructor of the class. Initialize the storage
* @param storage The store to work with
*/
public Consumer(EventStorage storage){
this.storage=storage;
}
/**
* Core method for the consumer. Consume 100 events
*/
@Override
public void run() {
for (int i=0; i<100; i++){
storage.get();
}
}
}
主类:Main:(分别启动一个生产者和一个消费者线程)
/**
* Main class of the example
*/
public class Main {
/**
* Main method of the example
*/
public static void main(String[] args) {
// Creates an event storage
EventStorage storage=new EventStorage();
// Creates a Producer and a Thread to run it
Producer producer=new Producer(storage);
Thread thread1=new Thread(producer);
// Creates a Consumer and a Thread to run it
Consumer consumer=new Consumer(storage);
Thread thread2=new Thread(consumer);
// Starts the thread
thread2.start();
thread1.start();
}
}
运行截图如下所示:
3. 永远不要在循环之外调用wait方法
《Effective Java》第二版中文版第69条244页位置对这一点说了一页,我看着一知半解。我能理解的一点是:对于从wait中被notify的进程来说,它在被notify之后还需要重新检查是否符合执行条件,如果不符合,就必须再次被wait,如果符合才能往下执行。所以:wait方法应该使用循环模式来调用。按照上面的生产者和消费者问题来说:错误情况一:如果有两个生产者A和B,一个消费者C。当存储空间满了之后,生产者A和B都被wait,进入等待唤醒队列。当消费者C取走了一个数据后,如果调用了notifyAll(),注意,此处是调用notifyAll(),则生产者线程A和B都将被唤醒,如果此时A和B中的wait不在while循环中而是在if中,则A和B就不会再次判断是否符合执行条件,都将直接执行wait()之后的程序,那么如果A放入了一个数据至存储空间,则此时存储空间已经满了;但是B还是会继续往存储空间里放数据,错误便产生了。错误情况二:如果有两个生产者A和B,一个消费者C。当存储空间满了之后,生产者A和B都被wait,进入等待唤醒队列。当消费者C取走了一个数据后,如果调用了notify(),则A和B中的一个将被唤醒,假设A被唤醒,则A向存储空间放入了一个数据,至此空间就满了。A执行了notify()之后,如果唤醒了B,那么B不会再次判断是否符合执行条件,将直接执行wait()之后的程序,这样就导致向已经满了数据存储区中再次放入数据。错误产生。
下面是错误情况二的演示代码。根据第二节的代码修改而来:
数据存储类:EventStorage:(set中使用if代替while判断执行条件)
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* This class implements an Event storage. Producers will storage
* events in it and Consumers will process them. An event will
* be a java.util.Date object
*
*/
public class EventStorage {
/**
* Maximum size of the storage
*/
private int maxSize;
/**
* Storage of events
*/
private List<Date> storage;
/**
* Constructor of the class. Initializes the attributes.
*/
public EventStorage(){
maxSize=10;
storage=new LinkedList<>();
}
/**
* This method creates and storage an event.
*/
public synchronized void set(){
if (storage.size()>=maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.printf("Set: %d\n",storage.size());
notify();
}
/**
* This method delete the first event of the storage.
*/
public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll());
notify();
}
}
生产者类:Producer:(调用EventStorage类中的set方法存入数据,没有修改)
消费者类:Consumer:(调用EventStorage类中的get方法取出数据,在run方法中加入了一个1ms的休眠)
import java.util.concurrent.TimeUnit;
/**
* This class implements a consumer of events.
*
*/
public class Consumer implements Runnable {
/**
* Store to work with
*/
private EventStorage storage;
/**
* Constructor of the class. Initialize the storage
* @param storage The store to work with
*/
public Consumer(EventStorage storage){
this.storage=storage;
}
/**
* Core method for the consumer. Consume 100 events
*/
@Override
public void run() {
for (int i=0; i<100; i++){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.get();
}
}
}
主类:Main:(启动了两个生产者线程和一个消费者线程)
/**
* Main class of the example
*/
public class Main {
/**
* Main method of the example
*/
public static void main(String[] args) {
// Creates an event storage
EventStorage storage=new EventStorage();
// Creates a Producer and a Thread to run it
Producer producer=new Producer(storage);
Thread thread1=new Thread(producer);
Thread thread3=new Thread(producer);
// Creates a Consumer and a Thread to run it
Consumer consumer=new Consumer(storage);
Thread thread2=new Thread(consumer);
// Starts the thread
thread2.start();
thread1.start();
thread3.start();
}
}
程序运行截图如下:说明存储数据区已经错误的存储了超过规定的最大存储量的数据。并发错误。
写在最后
像我一样的老程序员们,醒醒吧,学习学习java.util.concurrent包吧;学习学习java7和java8的新特性吧。再不学习,我们就要被淘汰了!
参考资料:
《Java7并发编程实战手册》
《Effective Java》第二版中文版