Understanding Threads, Monitors and Locks

4 years ago by in Articles, Concurrency Tagged: , , , , , , , , ,

Java was designed to be a multi-threaded language and creating threads is as simple as instantiating objects. On the other hand, making correct use of threads is not as straight forward as one would think. The main challenge is not how to create threads, but how to properly use them and how to guard against integrity violations due to incorrect use of threads.

In this article we will see how to use threads and how to coordinate their flow by making proper use of monitors and locks. The reader is required to have an understanding of threads and know what these are and why these are used. On the other hand, the reader does not need to have any experience with threads. Please note that we will not explore any frameworks that simplifies the use and management of threads such as the concurrency API (tutorial) in this article.

Simple Thread

As stated before creating a thread is very simple as shown in the following example.

package com.javacreed.examples.concurrency.part1;

public class CreateSimpleThread {

  private static void log(final String message) {
    System.out.printf("%tT [%s] %s%n", System.currentTimeMillis(), Thread.currentThread().getName(), message);
  }

  public static void main(final String[] args) throws InterruptedException {
    CreateSimpleThread.log("Start");

    final Thread t = new Thread("My Thread") {
      @Override
      public void run() {
        CreateSimpleThread.log("Hello from thread");
      }
    };
    t.start();

    CreateSimpleThread.log("Waiting for thread to finish");
    t.join();

    CreateSimpleThread.log("Done");
  }
}

This class has two methods, the log() methods used to print formatted messages to the command prompt and the main() method. In the main() method a thread is created and executed as described next.

  1. The main method creates a thread called t and overrides the run() method as shown below.
      final Thread t = new Thread("My Thread") {
          @Override
          public void run() {
            CreateSimpleThread.log("Hello from thread");
          }
        };
    

    We are passing a user-friendly name to the Thread constructor. This is very useful when dealing with many threads as the threads will have the given name rather than a generated one.

    Instead of overriding the run() method in the Thread class, we can use the following approach.

        final Runnable r = new Runnable() {
          @Override
          public void run() {
            CreateSimpleThread.log("Hello from thread");
          }
        };
        final Thread t = new Thread(r, "My Thread");
    

    With the alternative approach, we first create an instance of Runnable and the pass this to the Thread constructor’s argument. This approach is preferred over the previous one as it avoids conflicts with methods available in the Thread class, but it requires the creation of another object.

    In this example, both approaches will yield the same result.

  2. Once the thread is created, it needs to be started.

        t.start();
    

    This is done by invoking the start() method on the thread object as shown above. The start() method will cause a new thread to be started and the run() method is executed by the newly started thread. The following code is executed by the thread we just created.

            CreateSimpleThread.log("Hello from thread");
    

    It is important to note that the start() method will not wait for the thread to finish and will immediately return the control to the next statement. Furthermore, invoking the thread’s run() will not start a new thread and will block until the method is complete.

  3. The join() method, will block until the thread has finish execution. This is ideal when you need to wait for the thread to finish before doing something else.

        t.join();
    

    While it is important to start the thread, it is not always required to wait for it to finish and in many cases we start threads and do not wait for them to complete.

When executing the above class we will get the following in the output.

11:24:02 [main] Start
11:24:02 [main] Waiting for thread to finish
11:24:02 [My Thread] Hello from thread
11:24:02 [main] Done

Note that each message entry also contains the thread name from where this was executed. Here we have three log messaged from the main thread and one message from our thread called My Thread. The main thread is the first thread created and is responsible from executing the main() method. As you may have understood already, in Java, everything is executed within a thread and you can have as many threads as you need (given that there are enough resources to sustain all the threads). Java will always start the application in the main thread.

Now that we saw how to create threads, let see and understand better how to use them.

The Problem

Let say that we have a group of persons who are all talking at the same time as shown in the following figure. Is it possible to follow and understand what is being said?

Everyone talking at the same time

Everyone talking at the same time

No, it is not easy to understand what is being said as many conversations are mixed together as shown below.

Intertwine Conversations

Intertwine Conversations

The end result is very hard to understand as what was meant to be a set of individual sentences, ended up to be a single sentence, with all sentences intertwine. This compromise the integrity of the end result as shown in the following example. Consider the following Shakespeare quotes (reference):

  1. To be, or not to be
  2. O Romeo, Romeo! wherefore art thou Romeo?

Now let’s intertwine them as shown next.

To O Romeo, be, or Romeo! not to wherefore art thou be Romeo? 

The result obtained does not make sense and is hard to understand what is being said. Worse than that, the new sentence may be syntactically correct and may have a totally different meaning that the original two sentences.

How do we fix that?

The solution is not very complex. On the contrary, it is quite simple. We need to introduce some kind of order so that only one person speaks at a given point in time. In order to make it simple to the participants we will make use of a microphone. We will only have one microphone and only that person who has the microphone can talk as shown below.

Only One Person Talks

Only One Person Talks

The others will have to wait for their turn to talk. Now we can easily follow what is being said without any interference from the other people.

Let us take this one step further. Say we have two independent groups of people that would like to communicate with each other using the above protocol. How do we address that? All we need to do is to include as many microphones as we have groups and each group will have its own microphone. The second group should not wait for the first group to finish with the microphone in order for them to talk as shown next.

Two Independent Groups

Two Independent Groups

What this has to do with threads, monitors and lock?

The same analogy can be applied to threads, monitors and locks. Say we have several threads and these require modifying an object. If all threads modify the object without any flow control, this object may end-up in an unexpected (illegal) state. The following figure shows the similarities between the previous examples and threads, monitors and locks.

Threads, Monitors and Locks

Threads, Monitors and Locks

As shown in the above figure, the threads are the people, the microphone is the monitor and the acquisition of the microphone is the lock. The threads need to acquire a lock on the monitor before they can talk (execute). Without the lock on the monitor the thread will not execute. Therefore a monitor is another object used to control who goes next. In the above example, only the thread represented by the female person has the lock and thus only it is executing. The other threads, represented by the other persons have to wait.

Let’s see this with an example.

Simple Example

Consider the following simple class, called SimpleCounter where we have one field and four methods that work with this field.

package com.javacreed.examples.concurrency.part1;

public class SimpleCounter {

  private int value;

  public int getValue() {
    return value;
  }

  public void incrementValue() {
    value++;
  }

  public void setValue(final int value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }
}

This class is not thread-safe as it does not make use of monitors and locks. Therefore the counter value may be incorrect when accessed from multiple threads. To make things worse, you may try to access and modify this object by multiple threads and this object may seems to work well. This is a false assurance and the above object will break if access by multiple threads.

How do we fix that?

This can be fixed by using the synchronized (tutorial) keyword as shown next.

package com.javacreed.examples.concurrency.part1;

import net.jcip.annotations.ThreadSafe;

@ThreadSafe
public class ThreadSafeCounter {

  private int value;

  public synchronized int getValue() {
    return value;
  }

  public synchronized void incrementValue() {
    value++;
  }

  public synchronized void setValue(final int value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return String.valueOf(value);
  }
}

The solution is quite easy, but before we apply synchronized on all of our code, let us first understand what is happening here. The synchronized keyword takes the following form:

synchronized(monitor) {
  code protected by the monitor
}

The class ThreadSafeCounter makes use of synchronized methods (tutorial) which are equivalent to the following:

synchronized(this) {
  method body
}

When using the synchronized methods to protect the objects, as we did in the ThreadSafeCounter class, the object instance acts as a monitor. In other words, our object is the microphone. A lock needs to be acquired on monitor (our object instance) before the code within the block can be executed by the thread. A lock can only be held by one thread. Therefore if two, or more threads, try to acquire a lock on the same monitor (object instance) at the same time, only one of them will succeed and the other threads have to wait for this lock to be released before they can acquire it. The following figure illustrated this.

Methods and Threads

Methods and Threads

Thread 1 owns the lock on this object as it is executing the method getValue(). Therefore, thread 2 has to wait for thread 1 to finish before it can access the incrementValue() method, as this method is protected by the same monitor. Note that the methods: getValue(), incrementValue() and setValue(int) are all protected with the same monitor, therefore only one thread can access these methods at a given point in time. Thread 3 does not need to wait as the toString() method is not protected/guarded by any monitors/locks. The toString() can be accessed freely by any thread at any point in time.

As stated before, the object is acting as the monitor. Therefore, if we have two different objects we will also have two different monitors. In the following code fragment we created two different objects and these are referenced through variables counter1 and counter2.

ThreadSafeCounter counter1 = new ThreadSafeCounter();
ThreadSafeCounter counter2 = new ThreadSafeCounter();

Observation

Is it possible for thread 2 to access the counter2.incrementValue() while thread 1 is accessing the counter1.getValue() method?
Yes it is. Note that we have a monitor for every object and while one thread owns the lock on one monitor, the other thread can own the lock on a different monitor.

The synchronized methods are very easy to use and in many cases these are misused or misunderstood. When working with threads it is imports to understand what monitors are being used to guard what. If the wrong monitor is used, then what it seems to be well protected code ends up in broken code. For example, say we have two microphones and two persons in the same group take each microphone. Using the protocol described above (that is, only the person with the microphone can speak while others have to wait), how many persons will be able to speak simultaneously? As described before, those who have the microphone can speak, thus two persons are able to speak simultaneously. This will produce unwanted results as we shown in the following figure.

Two Monitors

Two Monitors

The problem here is caused by the use of two monitors to guard the same object. The solution is very simple. All we need to do it to remove the second microphone and the problem is solved. The same analogy can be applied to Java. All we need to do is agree on a monitor an always use this monitor.

Complex Example

We saw how to handle objects when used by multiple threads in simple cases. Consider the following simple class called ValuePair.

package com.javacreed.examples.concurrency.part1;

/**
 * This class has two fields, {@link #a} and {@link #b}, which should always have the same value. The value
 * can only be set through one method: {@link #setValues(int)}.
 * 
 * @author Albert
 * 
 */
public class ValuePair {

  private int a;
  private int b;

  public synchronized void copy(final ValuePair other) {
    this.a = other.a;
    this.b = other.b;
  }

  public synchronized void setValue(final int value) {
    this.a = value;
    this.b = value;
  }

  @Override
  public synchronized String toString() {
    return String.format("a: %d and b: %d", a, b);
  }

}

The class itself is very simple. It has two fields and two methods that access these fields. Consider the following question:

  • Is this class thread safe?
  • Does this class do what it states to do when used by multiple threads?
  • Is it possible for the fields a and b to have a different value?

As one would expect this class is not thread safe. The tricky bit is to determine why this is not thread-safe. The problem lies in the copy() method as this method acquires only one lock on one of the objects as described next. Consider the following scenario:

package com.javacreed.examples.concurrency.part1;

public class UsingValuePair {
  public static void main(final String[] args) throws InterruptedException {
    final ValuePair object1 = new ValuePair();
    object1.setValue(1);

    final ValuePair object2 = new ValuePair();
    object2.setValue(5);

    final Thread thread1 = new Thread("Thread 1") {
      @Override
      public void run() {
        object1.copy(object2);
      }
    };

    final Thread thread2 = new Thread("Thread 2") {
      @Override
      public void run() {
        object2.setValue(12);
      }
    };

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();
    
    System.out.println(object1);
  }
}

Here we have two objects and two threads. The following steps describe a sequence of actions that, if these occur in the order they are described, the object in question will be left in an invalid (illegal) state.

  1. Thread 1 copies the value field a from object 2 to object 1.
  2. Thread 2 invokes the method setValue() on object 2, while thread 1 is still copying the values from object 2 to object 1. Note that so far thread 1 has only copied the value of field a. Field b still has its original value.
  3. Thread 1 copies the value field b from object 2 to object 1. Note that by this time, thread 2 has already updated object 2 and field b now has the new value.

If executed in this order, object 1 will be left in an invalid (illegal) state, as its fields a and b will have a different value. This is different from what is documented.

How do we fix that?

Before fixing any problem we need to understand what is causing the problem. The copy method is only acquiring a lock on the current object. But here we have two objects: this and the other. We need to lock the other object too while reading its values and then lock this when updating the values.

Consider the following solution.

  public synchronized void copy(final ValuePairDeadLock other) {
    synchronized (other) {
      this.a = other.a;
      this.b = other.b;
    }
  }

This approach seems to be reasonable, and solves the problem. Unfortunately, it creates another (possibly worse) problem referred to dead-lock (tutorial). If both threads try to copy from one to the other as shown next, the code can stay hanging forever as thread 1 will be waiting for thread 2 to release its lock, while thread 2 will be waiting for thread 1 to release its lock.

    final Thread thread1 = new Thread("Thread 1") {
      @Override
      public void run() {
        object1.copy(object2);
      }
    };

    final Thread thread2 = new Thread("Thread 2") {
      @Override
      public void run() {
        object2.copy(object1);
      }
    };

Unfortunately, synchronized methods do not provide timeout functionality. Therefore when a thread enters a deadlock, it stays in the deadlock forever (until the program is terminated).

A better solution is shown next.

  public void copy(final BetterValuePair other) {
    final int a, b;
    synchronized (other) {
      a = other.a;
      b = other.b;
    }

    synchronized (this) {
      this.a = a;
      this.b = b;
    }
  }

The copy() method is not making use of synchronized methods anymore. It first acquires a lock on the other object. Then it saves the other object values into two temporary variables. While the thread executing the copy() method has the lock, no other thread can modify the values of the other object. It then releases the lock on the other object. Once the lock is released, it acquires the lock on this object and sets the values to those of the temporary variables created before. Note that the new version of this method only has one lock at a given point in time. In other words, the locks do not overlap. This solves the deadlock problem as only one lock is held at a given point in time. Furthermore, this approach also fixed the invalid (illegal) state problem. With the approach, the objects will always be in a valid state.

Reentrant Lock

What will happen if a thread holding a lock on an object invokes another method guarded by the same lock? Consider the following example.

package com.javacreed.examples.concurrency.part1;

public class ReentrantSynchronizationExample {

  public synchronized void a() {
    System.out.println("Calling method b() from a()");
    b();
  }

  public synchronized void b() {
    System.out.println("Calling method c() from b()");
    c();
  }

  public synchronized void c() {
    System.out.println("Hello from method c");
  }
}

This class has three methods, and all these methods are synchronised. Method a(), invokes method b(), which in turn invokes method c().

Java supports what is called Reentrant Lock (tutorial), where the same thread can acquire the same lock on the same monitor multiple times. When the thread calls method a() it acquires the lock. Java keeps a counter of how many times the lock is acquired by the thread owning the lock. In this case the counter will read 1, as this thread has only accessed method a(). When method b() is invoked from a(), Java will increment the counter to 2, as now this thread owns the lock twice. The lock will be released only when the counter reaches 0. When method c() is invoked from b(), Java will increment the counter again. Therefore, while executing method c(), the counter will read 3. When method c() finishes execution, the counter is decremented to 2. Same happens when method b() finishes execution, the counter is decremented once more to 1. When method a() finishes execution, the counter is decremented once more and the lock is released as the counter reached 0.

While a thread is executing method a(), no other thread can invoke any of these methods as these are protected by the same monitor. It is very important to understand that when invoking b() through method a() the lock is never released. If it was so, another thread may acquire the lock and compromise the integrity of the object.

While this may sound complex, Java handles all this behind the scenes and no intervention is required from the developer to signal Java to increment or decrement the counter. Furthermore, any thrown exceptions do not affect the counter as this is decremented accordingly.

Conclusion

Threads are simply to use and when used properly they can make a good program even better.

One of the most important things when using threads is the monitor guarding the object. If the monitor is not well understood, then there can be the risk that at a random point in time the code will start behaving in mysterious ways as we saw in the examples above. Such problems are very hard to identify and reproduce but can have devastating effect on the system.

Albert Attard

Albert Attard is a Java passionate and technical lead at a research group. You can find him on . Over the past years Albert worked on various Java projects including traditional server/client applications, modular applications, large data handling applications and concurrent data manipulation applications to name a few. He has a BSc degree from the University of London (Homepage) and an MSc Information Security with the same university. His MSc thesis (Book) received the 2012 SearchSecurity.co.UK award (Website).

7 Responses to “Understanding Threads, Monitors and Locks”


Volodymyr
August 27, 2013 Reply

Hello!

I think the main thing this article lacks is continuation. It would be great to find out more of ReenTrantLock and Executors,etc…

Albert Attard Albert Attard
August 27, 2013 Reply

Thank you for your feedback. Reentrant locks will be included too amongst other things.

Please note that this article is not yet complete but hopefully will be ready soon.

Shashank Srivastava
April 13, 2016 Reply

My hearty wishes to Albert for such a beautiful explanation.

I have one question here-
Do threads waiting in the hallway can perform the task on unsynchronized methods or blocks?

Albert Attard Albert Attard
April 14, 2016 Reply

Thank you for your comment. Can you please explain your point further as I did not quite understand it?

Albert

Mike
March 27, 2015 Reply

Thank you man, for good and simple explained things about concurrency in java.
It helps me better understand some things.

TIM
November 13, 2015 Reply

Nice job, very userful,thank you

priya
March 19, 2018 Reply

A Java program can have many threads, and these threads can run concurrently, either asynchronously or synchronously. Many thanks for sharing this.

Leave a Comment


Time limit is exhausted. Please reload the CAPTCHA.