Tuesday, February 17, 2015

Why wait should be used inside a loop?

Sometimes, people are not sure why we should use wait inside a loop instead of using an if condition. In short, notify/notifyAll can be called by some other parts of the code and your code can fail if you don't check the condition in the (for) loop

Easy to understand with an example -

public class SynchronizationMain {
  public static void main(String[] args) {
      final EventStorage storage = new EventStorage();
      startEvilThread(storage);
      final Producer producer = new Producer(storage);
      final Thread producerThread = new Thread(producer);
      final Consumer consumer = new Consumer(storage);
      final Thread consumerThread = new Thread(consumer);
      producerThread.start();
      consumerThread.start();
  }

  public static void startEvilThread(final EventStorage storage) {
      Runnable evilThread = new Runnable() {
      public void run() {
      while(true) {
      synchronized(storage) {
      storage.notifyAll();
      }
      }
      }
      };
  Thread wrapper = new Thread(evilThread);
  wrapper.start();
  }
}

public class EventStorage {
  private int maxSize;
  private Queue storage;

  public EventStorage() {
  maxSize = 10;
  storage = new LinkedList();
  }

  public synchronized void set() {
  while (storage.size() == maxSize) {
  try {
  wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  storage.offer(new Date());
  System.out.printf("Set: %d",storage.size());
  System.out.println("");
  notifyAll();
  }

  public synchronized void get() {
  while (storage.size() == 0) {
  try {
  wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  System.out.printf("Get: %d: %s",storage.size(), storage.poll());
  System.out.println("");
  notifyAll();
  }
}

public class Producer implements Runnable {
  private EventStorage storage;

  public Producer(EventStorage storage) {
  this.storage = storage;
  }

  @Override
  public void run() {
  for(int i = 0; i < 100; i++) {
  storage.set();
  }
  }
}

public class Consumer implements Runnable {
  private EventStorage storage;
  public Consumer(EventStorage storage) {
      this.storage = storage;
  }

  @Override
  public void run() {
      for(int i = 0; i < 100; i++) {
      storage.get();
  }
  }
}

Run the same program before and after changing while loop to if in set and get method of EventStorage class and you should notice the difference.

No comments: