-1

An interesting thing just occurred to me when trying to have a simple demo using wait() with synchronized, the following demo is giving me unexpected outputs.

public class WaitZero {
    private static AtomicInteger num = new AtomicInteger(0);
    private static boolean consumed = false;

    public static void main(String... args) throws Exception {
        ThreadPoolExecutor threadPoolExecutor = getMyCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(WaitZero::send);
            threadPoolExecutor.submit(WaitZero::receive);
        }
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static synchronized void send() {
        try {
            while (!isConsumed()) {
                num.wait();
            }
        } catch (InterruptedException ignored) {
            ignored.printStackTrace();
        }
        num.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + " number updated: " + num);
        setConsumed(false);
        num.notifyAll();
    }

    private static synchronized void receive() {
        try {
            while (isConsumed()) {
                num.wait();
            }
        } catch (InterruptedException ignored) {
            ignored.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " number received: " + num);
        setConsumed(true);
        num.notifyAll(); // ToDo: when to use notify?
        // ToDo: what is monitor?
    }

    private static boolean isConsumed() {
        return consumed;
    }

    private static void setConsumed(boolean consumed) {
        WaitZero.consumed = consumed;
    }
}

It's output is not stable but one of the typicals can be

shared-pool-0 number received: 0
shared-pool-1 number updated: 1
shared-pool-0 number received: 1
shared-pool-1 number updated: 2
shared-pool-1 number received: 2
shared-pool-2 number updated: 3

While what I was expecting is

shared-pool-1 number received: 0
shared-pool-0 number updated: 1
shared-pool-3 number received: 1
shared-pool-2 number updated: 2
shared-pool-1 number received: 2
shared-pool-0 number updated: 3
shared-pool-2 number received: 3
shared-pool-3 number updated: 4
shared-pool-5 number received: 4
shared-pool-4 number updated: 5

The correct result is retrieved when I use WaitZero.class instead of num on wait()/notifyAll().

I've read around and it seems it's always the case that three of them have to be used on the same object to ensure correctness.

My Guess: if not all of them on the same object, there is a special case between the notifyAll() and the synchronised lock. But what is it?

Any help will be appreciated ;)

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Hearen
  • 7,420
  • 4
  • 53
  • 63
  • 3
    You're methods are synchronized and, since they're static, that means it uses the method's enclosing class' monitor. That's why it works when you call the `wait`/`notify`/`notifyAll` methods on `WaitZero.class`, because you're synchronizing on the same `Class` object. It doesn't work with `num` because you never synchronize on it—nor should you as it's a non-final, actively-changing reference. – Slaw Mar 30 '19 at 08:28
  • 2
    both wait and notifyAll require that the thread holds the lock monitor. They never do, and thus always throw an IllegalMonitorStateException. And you keep assigning a new value to num by using num++, so you don't even call wait and notifyAll on the same object. 1. Don't use a shared value like 0 as a lock. Use a private, final lock object, that you create yourself. 2. always synchronize, wait and notify on the lock. 3. avoid these low-level, bug-prone synchronization primitives in the first place. use higher-level abstraction from java.util.concurrent. – JB Nizet Mar 30 '19 at 08:29
  • @JBNizet Thanks for the help ;) As far as I know `private static Integer num = 0;` since it's already `static`, it's reference should be always the same, isn't it? Some points you enclosed, I just read them several minutes ago. What I want to really know is **why?** – Hearen Mar 30 '19 at 08:32
  • @slaw what do you mean by "actively-changing reference", it's static already. – Hearen Mar 30 '19 at 08:33
  • 2
    @Hearen Integers are immutable, so when you do `num++` you are creating a new Integer instance with the incremented value. so the object changes. – Fullstack Guy Mar 30 '19 at 08:34
  • 2
    No. `num++` is equivalent to `num = Integer.valueOf(num.intValue() + 1)`. It assigns another object to the `num` variable. And `0` is a shared, cached Integer value. So any other thread couldd also synchronize on it and thus cause interferences, deadlocks, etc. – JB Nizet Mar 30 '19 at 08:35
  • Wow, just...got it now...I am testing using Object then. Really appreciate the details... – Hearen Mar 30 '19 at 08:36
  • @JBNizet, I just tested with `AtomicInteger` with its methods invoking, in that case the result is the same. Only when I use the `synchronised(num)`, the result can be correct. => num here is already defined as an `AtomicInteger`. – Hearen Mar 30 '19 at 08:41
  • 3
    Again, both wait and notifyAll require that the thread holds the lock monitor. They never do, and thus always throw an IllegalMonitorStateException. – JB Nizet Mar 30 '19 at 08:42
  • 1
    You have mis-stated the requirement. If you call `wait()` or `notify()` you must also be synchronized on the same object. This follows rather obviously from the semantics, and is discussed extensively in the existing documentation. Unclear why you need further assistance with this. – user207421 Mar 30 '19 at 08:47
  • @user207421 Sorry for my silly questions, I just read docs and posts but I couldn't get the point and now I suppose I get it now...finally, sorry for the inconvenience. Thank you for all the help ;) – Hearen Mar 30 '19 at 08:48

1 Answers1

0

After lots of naive questions and great help from @JB Nizet, @Amardeep Bhowmick and all, I found a pithy sentence from How to work with wait(), notify() and notifyAll() in Java? explain the reason exactly.

wait() method is designed/used to give up the lock (since some condition not met) to let other threads work/cooperate; a typical use case is a sender/receiver or a producer/consumer.

wait()

It tells the calling thread to give up the lock and go to sleep until some other thread enters the same monitor and calls notify()...The wait() method is actually tightly integrated with the synchronization lock, using a feature not available directly from the synchronization mechanism.

synchronized(lockObject) {
    while( ! condition ) {
        lockObject.wait();
    }
    //take the action here;
}

In that case, the issue can be simply fixed as following or just use WaitZero.class for wait/notifyAll.

public class WaitZero {
    private static AtomicInteger num = new AtomicInteger(0);
    private static boolean consumed = false;

    public static void main(String... args) throws Exception {
        ThreadPoolExecutor threadPoolExecutor = getMyCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(WaitZero::send);
            threadPoolExecutor.submit(WaitZero::receive);
        }
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static void send() {
        synchronized (num) {
            try {
                while (!isConsumed()) {
                    num.wait();
                }
            } catch (InterruptedException ignored) {
                ignored.printStackTrace();
            }
            num.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + " number updated: " + num);
            setConsumed(false);
            num.notifyAll();
        }
    }

    private static void receive() {
        synchronized (num) {
            try {
                while (isConsumed()) {
                    num.wait();
                }
            } catch (InterruptedException ignored) {
                ignored.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " number received: " + num);
            setConsumed(true);
            num.notifyAll(); // ToDo: when to use notify?
            // ToDo: what is monitor?
        }
    }

    private static boolean isConsumed() {
        return consumed;
    }

    private static void setConsumed(boolean consumed) {
        WaitZero.consumed = consumed;
    }
}
Community
  • 1
  • 1
Hearen
  • 7,420
  • 4
  • 53
  • 63
  • There’s no sense in using an `AtomicInteger` when all access to the variable is `synchronized`. Besides that, all you found out, is what has already been written in the [original documentation of `wait`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--) – Holger Apr 01 '19 at 11:37
  • @Holger, good morning, sorry to reply this late. I am using `AtomicInteger` is indeed unnecessary perhaps I can just use `Long` instead to avoid `IntegerCache` reference changing issues. Yes, all I found is presented directly in the doc but at that time I read it times and still couldn't get it; that's why I was struggling with it. As always said, you will never understand it until blundered. As a matter of fact, lots of questions can be traced back to the official doc or tutorial but as long as it confuses me, it could confuse someone else. Have a nice day ;) – Hearen Apr 02 '19 at 00:27
  • 1
    You can use a plain `int` and use a different object for synchronization. I.e. in your original code you had the methods declared as `static synchronized`, which used `WaitZero.class` for locking implicitly, combinable with `WaitZero.class.wait()` and `WaitZero.class.notify()`. Or create a field like `private static final Object LOCK = new Object();` in additional to the `int` field and use `synchronized(LOCK) { … }`. You are right, reading the specification does not always help, but it’s worth using it not only as a starting point, but also re-read it later-on with more understanding. – Holger Apr 02 '19 at 07:54
  • 1
    You might read [this](https://stackoverflow.com/a/54924662/2711488) and [that](https://stackoverflow.com/a/27983573/2711488) answer additionally, these are written for `Lock` implementations, but the logic applies to `synchronized` as well. – Holger Apr 02 '19 at 07:59
  • @Holger Thank you for providing the more readable answers. Quite thoughtful and obliging you are, thank you so much! I will look into them and get more hold of this topic. Thank you, Holger – Hearen Apr 02 '19 at 08:12