Object monitor pattern

Classes synchronization in Java multi-threading environment can be done thanks to many classes from java.util.concurrent package. But it can also be achieved thanks to some traditional methods, as monitors.

In the first part we'll define the concept of monitor. This part will also explain two types of thread synchronization. These two types will be illustrated by Java code in the second part of the article.

Java monitors

Monitors are common concept retrieved in concurrent programming. They're used to support concurrent actions synchronization between threads. In practice, this technique helps to guarantee thread-safe access to object, method or class. A thread-safe access guarantees that a thread executing one method shared (accessible) by multiple threads, won't be disturbed by another threads. This thread will be able terminate its job in shared method in this way that another thread will be able to start to execute the method directly after.

When you're working with monitors, you should appropriate several concepts. The first concerns monitor region. A monitor region is the code that must be executed exclusively, from the begin to the end, by one and only one thread at given moment. The second concept is the acquiring of monitor region. When the region is acquired, there're no other thread that can acquire it again - only one region can be acquired at given time. After the acquire, thread can own the monitor region. It means that the region becomes (temporary) the property of one thread and only this thread can execute operations defined in owned region. When it terminates the execution, it releases the monitor region and makes it available to following threads waiting for acquiring. It's also the moment when monitor is released. To understand these two concepts, let's write a simple Java code and define monitor, monitor region, acquiring and releasing parts on it:

public class MonitorTest {

  @Test
  public void test() {
    final MonitorSample sample = new MonitorSample();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        sample.synchronizedMethod("Thread#1");
      }
    });
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        sample.synchronizedMethod("Thread#2");
      }
    });
    t1.start();
    t2.start();
  }
}

class MonitorSample {
        
  // this object plays the role of monitor
  private Object monitor = new Object();

  public void synchronizedMethod(String name) {
    // When thread is able to enter behind synchronized(monitor) {, it means that the
    // monitor region and monitor can be acquired by this thread
    synchronized(monitor) {
      // The code included between synchronized block is monitor region - the code that 
      // can be executed only by one thread at given moment, from the begin to the end.
      // When given thread access here, it owns the monitor region.
      System.out.println(name+" accessed here");
      // After the print, the monitor region and monitor are released.
    }
  }
}

Two types of monitors can be used:
- mutual exclusion: our code sample shows it - only one thread can execute given monitor region.
- cooperation: this kind of monitor helps two threads to achieve common goal. A good example of this is producer-customer logic where somebody (producer) produces something needed by someone else (customer). In Java, we can use this model when one thread needs the information prepared by another thread.

Java monitors - cooperation

To illustrate example of cooperation between two monitors, let's go into the world of writers and publishers. In the one side we have a very qualified writer who, unfortunately, always writes his books longer than publisher expects. In front of him we retrieve publisher who is very impatient to make some business and sell books written by writer. So, the publisher is always ready to publish before the book redaction. To do not lose the time for him, writer has to inform publisher as soon as he ends to write the last chapter of the book. Below code presents this situation:

public class MonitorWaitNotifyTest {

  public static void main(String[] args) {
    final Book book = new Book();
    
    final Thread writer = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("Writer run");
        synchronized(book) {
          try {
            System.out.println("W: Hey, I'm still writting this book. Please wait 5 seconds");
            Thread.sleep(5000);
            System.out.println("... 5 seconds after");
            System.out.println("W: The book is ready. I notify my publisher about it");
            book.notify();
          } catch (Exception e) {
            e.printStackTrace();
          }
        };
      }
    });
    Thread publisher = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("Publisher run");
        writer.start();
        synchronized(book) {
          try {
            System.out.println("P: I'm waiting for the book");
            book.wait();
            System.out.println("P: OK, I can publish the book");
          } catch (Exception e) {
            e.printStackTrace();
          }
        };
      }
    });
    publisher.start();
  }

}

class Book {
}

The code will output:

Publisher run
P: I'm waiting for the book
Writer run
W: Hey, I'm still writting this book. Please wait 5 seconds
... 5 seconds after
W: The book is ready. I notify my publisher about it
P: OK, I can publish the book

The output looks like the scenario described at the begin of this paragraph: publisher starts to waiting for the book while writer is terminating it. What did happen in this code ? First, note that two methods defined in java.lang.Object (so inherited by all Java's objects), wait and notify are playing the role of synchronizers. We can tell that they're more important that synchronized blocks. Why ?

Normally, when publisher acquires a lock on Book instance, writer shouldn't be able to acquire it too. So, we should receive all publisher's prints before all writer's prints. But it's not the case. It's because of wait() method specificity. When this method is called, acquired monitor on given object (book) is released. More, the thread that invokes wait() method on some object remains in waitint state. So, its execution doesn't continue. The execution can resume when another thread invokes notify() (or notifyAll() if two or more threads are waiting). wait-notify looks similar to barrier. But a name used to define wait-notify interactions is guarder blocks.

Java monitors - exclusion

To present exclusion monitor, we'll try to show programatically the situation where 5 people want to loan one house. Only one person can loan. But before it, they have all to make a visit. Only one visitor can visit the house simultaneously. They're the code to illustrate this situation:

public class MonitorExclusionTest {
  public static void main(String[] args) {
    VisitableHouse house = new VisitableHouse();
    Thread visitor1 = new Thread(new Visitor(house, "visitor#1"));
    Thread visitor2 = new Thread(new Visitor(house, "visitor#2"));
    Thread visitor3 = new Thread(new Visitor(house, "visitor#3"));
    Thread visitor4 = new Thread(new Visitor(house, "visitor#4"));
    Thread visitor5 = new Thread(new Visitor(house, "visitor#5"));
    visitor1.start();
    visitor2.start();
    visitor3.start();
    visitor4.start();
    visitor5.start();
  }

}

class Visitor implements Runnable {
  private VisitableHouse house;
  private String name;

  public Visitor(VisitableHouse house, String name) {
    this.house = house;
    this.name = name;
  }

  @Override
  public void run() {
    this.house.visit(this.name);
  }
}

class VisitableHouse {

  private Object visit = new Object();

  public void visit(String visitor) {
    System.out.println(visitor+" is waiting for the visit");
    synchronized(visit) {
      try {
        Thread.sleep(1000);
        System.out.println(visitor+" is visited the house");
      } catch (Exception e) {
        e.printStackTrace();
      }
    };
  }
}

This code should output:

visitor#1 is waiting for the visit
visitor#2 is waiting for the visit
visitor#3 is waiting for the visit
visitor#4 is waiting for the visit
visitor#5 is waiting for the visit
visitor#1 is visited the house
visitor#5 is visited the house
visitor#4 is visited the house
visitor#3 is visited the house
visitor#2 is visited the house

This code proves that only one thread can acquire monitor and execute the code from monitor region. The rest of threads will be in "pending" state because they will wait for monitor release.

This time we discovered the concept of monitor. We proved that it's some object, method or class used to guarantee that only one thread is executed given code (monitor region) at the same time and that thread can execute it from the begin to the end. In two last parts we saw two types of monitors, used to cooperate (guarded blocks) or handle exclusiveness.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!