Skip to main content

synchronized, wait() and notify() in java

synchronized keyword in java used to make sure that the code block would be executed by one and only one thread at a time.

Using the keyword one can write a code block which would be side effects free from other concurrently running thread.

There are two way we can use the keyword: 1) synchronized block and 2) synchronized method.

synchronized (lockingObject) {
    // code which must be executed by single thread at a time.
}

public synchronized void doWork () {
    // code which must be executed by single thread at a time.
}

The synchronized block locks on lockingObject whereas synchronized method locks on object of that method (this).

Good example of this is to achieve singleton pattern.

public class Singleton {
    private static Singleton s;
    public static Singleton getInstance () {
        if ( s == null ) {
            synchronized (Singleton.class) {
                if ( s == null ) {
                    s = new Singleton();
                }
            }
        }
        return s;
    }
}

Let's understand this in little bit more detail.

Suppose, thread 1 enter into getInstance method and pass first if condition. It enters into synchronized block and at the same time thread 2 enters into getInstance. Thread 1 passes second if condition too. Thread 2 stops at synchronized block because thread 1 already obtained lock on Singleton.class object. Now, thread 1 creates object and exits the block, so lock is released. Thread 2 can enter into synchronized block now. Thread 2 checks the second if condition but it is false and it gets same object which created by thread 1.

You might be wondering why there are two if statement. Only one inside the synchronized block can achieve our goal. But synchronization is expensive operation, so every time some thread try to create instance, we should not lock Singleton.class object.

Now, we can come to wait() and notify().

There is some scenario where we need to wait for some data required for further processing, which would be prepared by other thread. So, we need to wait for that other thread to complete its task.

Thread 1:

synchronized (lock) {
    /* Thread 2 cannot enter its synchronized block because thread 1 already entered */
    /* Doing something */
    lock.wait(); /* Waiting for thread 2 to finish its task. Now thread 2 can enter its synchronized block */
    /* Once the thread is notified, the code block would be executed using data prepared by thread 2 */
}

Thread 2:

synchronized (lock) {
    /* Entering into the block because thread 1 is waiting */
    /* Preparing data for thread 1 */
    lock.notify(); /* Notifies thread 1 that data required by it is ready now, it can start further execution /*
}

Ok.. so once we wait on some lock, the lock get released by the waiting thread. So other (notifying) thread can enters into its synchronized block locked by the locking object.

Once notifying thread finishes its task it can notify waiting thread on the locking object and sends a signal that once the thread exits from its synchronized block the waiting thread can gain lock on that object and can resume further execution.

Here is a tricky part with notify() and notifyAll() methods. It cannot release lock as soon as it is called. But, lock would be released once the synchronized block finishes its execution. The methods just send signal that waiting thread can obtain lock once the notifying thread release it.

Let's see pseudo code of Producer - Consumer problem.

class Producer {
    public void produce() {
        for ( loop forever ) {
            synchronized ( queue ) {
                if ( queue.isFull() ) {
                    queue.wait();
                } else {
                    queue.push(data);
                    queue.notify();
                }
            }
        }
    }
}

class Consumer {
    public void consume() {
        for ( loop forever ) {
            synchronized ( queue ) {
                if ( queue.isEmpty() ) {
                    queue.wait();
                } else {
                    queue.pop();
                    queue.notify();
                }
            }
        }
    }
}

Producer starts producing data into queue and consumer starts consuming. If queue is full than producer needs to wait for consumer to make it little empty so producer can carry on its task.

The pseudo code says that both have synchronized block on queue object. So at a time any one of them can write to it or read from it.

If queue is full, producer calls wait on it so consumer can enter into its synchronized block and consumes. Once consumer consumes data, queue becomes empty for one element. Consumer calls notify and sends signal to waiting producer that "Queue is little empty now you can start further processing as soon as I release lock on queue." So once synchronized block exits and releases lock, the producer can obtain lock and start producing.

The same happens when queue become empty and consumer need to wait for producer. Once producer produce something it notifies consumer to start consuming as soon as it exits synchronized block.

You might have noticed that suppose thread 2 first enters into its synchronized block and calls notify, what would happen. Producer every time calls notify after producing and consumer might not even waiting at that time, what would happen.

The notify signal simply ignored in this scenario. It is just effective if some thread is waiting on same locking object.

I hope that wait, notify and synchronization is clear now.

Comments

Popular posts from this blog

Basics of quantum computing: change of basis

Computing requires classical bits, which can hold a value of 0 or 1. Quantum computing gets benefit from superposition of quantum bit (qubit). Qubit is a representation of state of quantum object (electron, proton, photon, etc). The qubit holds a value between 0 and 1 while it is in superposition state. Superposition means the qubit possesses multiple values at the same time so we can harness its power for parallel computing. Once we try to measure the value it can be collapsed into either 0 or 1 based on probability. (Observer collapses wave function simply by observing.) Using bloch sphere, we can represent qubit physically. (The bloch sphere is a physical model to represent a spin state of qubit) A state vector can point to any direction from the center of the sphere. If a vector points to Z+ and Z-, it represents 0 and 1 state respectively. All other vectors represent superposition state in a Z basis. Now, if a vector points to X+, X-, Y+, Y-, all are at a 90-degree angle from

Monad: in programming

Monad is a structure with type constructor M and two functions unit ( η) and multiplication ( μ). and   To lift value from a to Ma we need unit function and to flatten from M(Ma) to Ma we need multiplication . Why do we need monad in functional programming? Why do we need functional programming anyways? Functional programming is a paradigm which uses pure functions to build an application. The power of pure functions are, we can compose them and we can build complex things out of the small and simple functions. If we have these functions   and   We can get   We can use below general purpose compose function to compose any two pure functions.   If we need to compose three functions then we can do like Now, we can't build application using only pure functions. It is next to impossible. We need side effects anyways to make the application practical in use. To achieve the goal, we need to keep pure functions in center/core part of application and need to move side effects outsid

Monad: to programming from category theory

We have seen the summarized article "monad in programming" in the previous post. Let's find out what is a monad in category theory and how the concept became relevant for functional programming. First, we need to understand the basics of category theory. Category A category consists of objects and morphisms (relationships) between those objects. More of it, the morphisms should be composable too. Below is an example of a category. Each dot is an object and each arrow shows morphism from some object to another. As arrows are composable, if there is a morphism from object a to object b , and there is another morphism from b to c , then we can find a morphism from a to c .  If you notice, there is identity morphism too, it exists for each object, but we have shown it just for x . It is a morphism from an object to itself. If we compose identity morphism with any other morphism w , we get the same w in return. Functor A functor is a relationship between categories