Lesson 11: Synchronization

Overview

When more than one thread needs to write or modify a single resouce, such as a data structure, the threads generally need to coordinate their access. Java has synchronization primitives as part of the languages. These include the synchronized keyword, the synchronized block, and the methods of the class java.lang.Object wiat, notify and notifyAll.

Examples

The details

In order to coordinate the access by threads to shared variables, Java associates a lock variable with each object. Blocks of code including entire methods of the object can be protected by this lock. Before a thread can enter the block or method, it must own the lock. A lock can be owned by at most one thread at a time. Upon leaving the method or block, the thread releases its claim on the lock.

The variables themselves are not protected by locks. In order to protect variables, access them only by code which is protected by locks. To enforce this strictly, make the variables private, so that only the properly synchronized methods of the class can access them.

To protect a method by the lock of its own object, include the keywork synchronized as a method modifier. A more general construction is to use the synchronized statement:

    synchronized ( expr )
    {

    }
where expr is an expression yeilding a non-null reference to any object. Before entering the body of the synchronized block, the thread must claim the lock of the reference expr. The synchronized keyword is equivalent to placing the entire body of the method in a synchronized(this) block.

The Java system also has a wait queue for each lock. A thread holding the lock can suspend itself, placing itself into the wait queue, simultaneously releasing its claim on the lock. Wait is a method of class Object. It looks like a method call into the object on which the lock is held. The thread does not return from this call until some other thread calls notify on the same lock variable. The thread must hold the lock of the object which it calls the wait, else a MonitorStateException is thrown at runtime. In general, this cannot be checked at compile time.

Notify is also a method call of object. A thread calling this method must hold the lock for the object, or a MonitorStateException is thrown at runtime. This method returns immediately. Notify will wake one thread which is currently sleeping in the wait queue of this lock; notifyAll will wake them all. Since the thread calling notify is still in synchronized code, it still holds the lock. Therefore, the awakened thread or threads will not immediately reenter their code. Exactly one will reenter as soon as the notifying thread leaves the synchronization block.

In the case of a synchronized method, the syntax works out simply. A bare call to wait is interpreted as this.wait, and the method is considered to be synchronized(this). Therefore the object on which the wait method is invoked will agree with the object on which the method is synchronized.

Although at most one thread can hold a lock, it can hold the lock multiple times. For instance:

   synchronized(this)
   {
      synchronized(this)
      {
            i++ ;
      }
   }
will increment i - the thread gets two claims on the lock for the this object. As a consequence, a thread in a synchronized method can call other synchronized methods of the same object without blocking. It will accumulate an additional lock for every method entered, and release one claim with every method left, either by return, error or exception.