Java synchronization: synchronized, wait(), notify()
Asked Answered
H

1

11

I am trying to understand inter-thread communication in Java, and read that the support comes by using: wait(), notify(), notifyAll() methods.

In order thread to execute any of these methods, the thread needs to be owner of object's lock for which thread is invoking (any of these) methods. In addition to this, all these methods needs to be in a synchronized block/method. So far good.

I tried to implement a program in which one thread prints odd numbers, and other thread prints even numbers.

The program works correctly, however, at the same time, it raised few more doubts.

Below is the complete source code of the program which I implemented.

PrintEvenNumThread.java // prints the even numbers

package com.example.multithr.implrun;

import com.example.common.ObjectToWaitOn;

public class PrintEvenNumThread implements Runnable {

    private ObjectToWaitOn objectToWaitOn;

    public PrintEvenNumThread(ObjectToWaitOn objectToWaitOn) {
        this.objectToWaitOn = objectToWaitOn;
    }

    @Override
    public void run() {

    int numToPrint = 2;

    for (;;) {
        synchronized (objectToWaitOn) {
            while(objectToWaitOn.getPrintEvenOrOdd() != 2) {
                try {
                    objectToWaitOn.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }   
            }
                objectToWaitOn.print("EvenThread",numToPrint);
                numToPrint += 2; // Generate next even number
                objectToWaitOn.setPrintEvenOrOdd(1);
                objectToWaitOn.notifyAll();
            }
        }
    }
}

PrintOddNumsThread.java // Prints the odd numbers

package com.example.multithr.implrun;

import com.example.common.ObjectToWaitOn;

public class PrintOddNumsThread implements Runnable {

    private ObjectToWaitOn objectToWaitOn;

    public PrintOddNumsThread(ObjectToWaitOn objectToWaitOn) {
        this.objectToWaitOn = objectToWaitOn;
    }

    @Override
    public void run() {
        int numToPrint = 1;

        for(;;) {

            synchronized(objectToWaitOn) {

                while(objectToWaitOn.getPrintEvenOrOdd() != 1) {  
                    try {
                        objectToWaitOn.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                objectToWaitOn.print("OddThread", numToPrint);
                numToPrint += 2; // Generate next odd number
                objectToWaitOn.setPrintEvenOrOdd(2);
                objectToWaitOn.notifyAll();
            }
        }
    }
}

ObjectToWaitOn.java // The "shared" object for inter-thread communication

package com.vipin.common;

public class ObjectToWaitOn {

    private int printEvenOrOdd;

    public ObjectToWaitOn(int printEvenOrOdd) {
        this.printEvenOrOdd = printEvenOrOdd;
    }

    public int getPrintEvenOrOdd() {
        return printEvenOrOdd;
    }

    public void setPrintEvenOrOdd(int printEvenOrOdd) {
        this.printEvenOrOdd = printEvenOrOdd;
    }

    public void print(String byThread, int numToPrint) {
        System.out.println(byThread + ": " +numToPrint);
    }
}

PrintEvenOddNumsMainApp.java

package com.example.multithr.main.app1;

import com.example.common.ObjectToWaitOn;
import com.example.multithr.implrun.PrintEvenNumThread;
import com.example.multithr.implrun.PrintOddNumsThread;

    public class PrintEvenOddNumsMainApp {

        public static void main(String[] args) {

            ObjectToWaitOn obj = new ObjectToWaitOn(1); // 1 == odd; 2 == even

            PrintEvenNumThread printEvenNumThread = new PrintEvenNumThread(obj);
            PrintOddNumsThread printOddNumsThread = new PrintOddNumsThread(obj);

            Thread evenNum = new Thread(printEvenNumThread);
            Thread oddNum = new Thread(printOddNumsThread);

            evenNum.start();
            oddNum.start();
        }
    }

My doubt is:

1) When any of these threads releases lock by calling notifyAll() on object objectToWaitOn (which is shared between these threads), does it release the lock immediately? I have this doubt because these threads are in synchronized block based on objectToWaitOn object; so even if a thread calls the notifyAll(), shouldn't it still hold the lock because it is in synchronized block?

2) When a thread is in waiting condition by calling wait() on objectToWaitOn, and if other thread released the lock by invoking notifyAll(), does the waiting thread waits for lock to release or something else? Doesn't a thread coming out of the synchronized block anyway release the lock on the object it holds; so in above example if a thread is holding lock on objectToWaitOn and comes out of the synchronized block, doesn't it anyway release the lock for objectToWaitOn, and shouldn't based on this the other thread wake up?

Can anyone help me clarify these doubts?

Haiphong answered 13/2, 2018 at 12:37 Comment(0)
C
7

Does it release the lock immediately?

No, it doesn't. The thread continues executing next statements within the synchronisation block.

Shouldn't it still hold the lock because it is in a synchronized block?

Yes, it should. A thread that calls the notify/notifyAll methods must hold the lock and will continue holding it until it leaves the synchronisation block normally or an exception happens:

  • If execution of the Block completes normally, then the monitor is unlocked and the synchronized statement completes normally.
  • If execution of the Block completes abruptly for any reason, then the monitor is unlocked and the synchronized statement completes abruptly for the same reason.
    JLS-14.19

The notify/notifyAll methods change the state of the threads1 that are waiting on this monitor from State.WAITING to State.RUNNABLE. When the threads are woken up, they can participate in acquiring the lock.

Coming up to the monitor, some of them2 might get the STATE.BLOCKED state and wait until the other thread releases the lock. Note that it doesn't require any notifications from the thread which holds the lock.

The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.
docs


1. In case of notify, it's a single arbitrary chosen thread.
2. Or all of them - if the thread that notified keeps holding the monitor.

Conciliatory answered 13/2, 2018 at 12:54 Comment(2)
What would happen if there is no notifyAll() and thread comes out of synchronized block? Isn't lock released anyways when thread comes out of synchronized block? Is it necessary that a thread waiting on wait() needs to be notified by other thread by notify() , even if lock may be released by the holding thread (by coming out of synchronized block and without any notify())Haiphong
@Haiphong the lock will be released, but waiting threads that are not notified will not switch to running (they stay in State.WAITING). You could use one of the timeout-versions, e.g. wait​(long timeout, int nanos) to ensure that waiting threads do not wait forever.Arriaga

© 2022 - 2024 — McMap. All rights reserved.