Mike
Mike

Reputation: 279

Java Thread wait and notify methods

I'm learning for OCJP and now I'm at "Thread" chapter, I have some questions about wait and notify methods. I think I understand what's happening here but I just want to make sure that I'm on the right way.I wrote this code as an example:

package threads;

public class Main {

    static Object lock = new Object();

    public static void main(String[] args) {
        new Main().new FirstThread().start();
        new Main().new SecondThread().start();
    }

    class FirstThread extends Thread {
        public void run() {
            synchronized (lock) {
                lock.notify();
                System.out.println("I've entered in FirstThread");
            }
        }
    }
    class SecondThread extends Thread {
        public void run() {
            synchronized (lock) {
                try {
                    lock.wait();
                    System.out.println("I'm in the second thread");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

In this example the console output is I've entered in FirstThread, because the first thread starts, the notify() method is called, then the second thread starts, the wait() method is called and the String "I'm in the second thread" is not printed.

The next scenario is that I reverse the positions of new Main().new FirstThread().start(); and new Main().new SecondThread().start(); the output is

I've entered in FirstThread
I'm in the second thread

because the second thread starts, the wait() method is called, then the first thread starts, the method notify() is called, the console prints I've entered in FirstThread, the wait is released and I'm in the second thread is printed in the console.

Is that happening because the computer is so fast and the threads run sequentially? Theoretically the second start() method can be called first in my opinion is it?.

And the last question I have, is why the lock Object must be static, because if I remove the static modifier the output is always I've entered in FirstThread?

I know that static fields are loaded in JVM when the class is loaded, but I can't understand the logic of lock Object.

Upvotes: 4

Views: 198

Answers (3)

Solomon Slow
Solomon Slow

Reputation: 27210

This is wrong because it doesn't change any shared state that the other thread can test:

synchronized (lock) {
    lock.notify();
    System.out.println("I've entered in FirstThread");
}

And this is wrong because it does not test anything:

synchronized (lock) {
    lock.wait();
    System.out.println("I'm in the second thread");
}

The problem is, lock.notify() does not do anything at all if there is no thread sleeping in lock.wait(). In your program, it is possible for the FirstThread to call notify() before the SecondThread calls wait(). The wait() call will never return in that case because the notify() call will do nothing in that case.

There's a reason why they make you enter a mutex (i.e., a synchronized block) before you can call wait() or notify(). It's because you are supposed to use the mutex to protect the shared shared state for which the waiter is waiting.

"shared state" can be as simple as a single boolean:

 boolean firstThreadRan = false;

The notifier (a.k.a., "producer") does this:

 synchronized(lock) {
     firstThreadRan = true;
     lock.notify();
     ...
 }

The waiter (a.k.a., "consumer") does this:

synchronized(lock) {
    while (! firstThreadRan) {
        lock.wait();
    }
    ...
}

The while loop is not strictly necessary in this case, but it becomes very important when more than one consumer is competing for the same event. It's good practice to always use a loop.

See https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html for a tutorial that explains wait() and notify() in more detail.

Upvotes: 1

AlexWien
AlexWien

Reputation: 28767

"Is that happening because the computer is so fast and the threads run sequentially? Theoretically the second start() method can be called first in my opinion is it?. "

Yes, you can introduce a sleep() with random time, for better (unit-) test, or demonstration purpose. (Of course, the final running code should not have that sleep)

And the last question I have, is why the lock Object must be static

Principally, it does not matter whether the lock is static or not, but you have to have the possibility to access it, and it must be the same lock object. (Not one object instance for each class). In your case it must be static, otherwise it would be two different objects instances.

Upvotes: 3

Mena
Mena

Reputation: 48444

The threads are started sequentially, and in theory thread 1 would execute before thread 2, although it's not guaranteed (pretty sure it'll be consistent in this simple case though, as there are no real or simulated aleatory delays).

This is why when thread 2 is started slightly before, it has a chance to wait on a lock that gets notified (by thread 1) subsequently, instead of waiting forever for a lock that's already been notified once (hence, no printing).

On the static lock Object: you're binding your [First/Second]Thread nested classes to instances of Main, so the lock has to be common to both if you want them to synchronize on the same lock.

If it was an instance object, your threads would access and synchronize on a different lock, as your new Main()... idiom would get two instances of Main and subsequently two instances of lock.

Upvotes: 3

Related Questions