7

I have tried to understand the necessity to implement threads by using wait() and notify() when accessing shared resources or relying on their state.

I see that the idea is to monitor objects and to wait for their availability and releasing them after use to make them usable for other threads/methods, but why are those methods necessary instead of just declaring the relevant objects as static volatile so that other threads learn about the change of state without invoking those methods?

For example

In a restaurant, there are 2 chefs. One of the chefs is a good chef (better cooking qualities, ..) and carries a boolean isGoodCook = true, while the second chef is a bad chef and carries a boolean isGoodCook = false.

There is only equipment for one chef to cook meals at a time. The bad chef cooks for a specific amount of time (= cookingTime) while the good chef comes into the kitchen occasionally to take over the bad chef's task of cooking meals. The good chef can never be interrupted in his cooking process and cooks for his entire cookingTime once he started.

(Bad chef stops cooking as long as the good chef takes the part of cooking meals (= cookingTime of good chef)).

And after the good chef stops cooking meals, the bad chef has to carry on the task of preparing meals once again.

private boolean cooking; //is equipment used at the moment
private boolean isGoodCook;
private boolean cookingDesire; //indicating to chef to stop cooking
private int cookingTime;


public CookingTask(boolean isGoodCook, int cookingTime)
{
    this.isGoodCook = isGoodCook;
    this.cookingTime = cookingTime;
}

public void run()
{  
    if(isGoodCook)
    {
        //notify actual cook to stop cooking
        cookingDesire = true; 
    }
    //wait til equipment to cook
    //is available
    while(cooking)
    {
        try 
        {
            wait();
        } catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
    //reserve equipment
    cooking = true;
    cookingDesire = false;
    //notify other threads (= bad cook)
    notifyAll();
    startCooking();
}

private void startCooking()
{
    for(int i = 0; i < cookingTime; cookingTime--)
    {
        try 
        {
            Thread.sleep(1000);
            //if good chef comes in
            if(cookingDesire)
            {
                //bad chef starts to pause
                startBreak();
            }
        }
        catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
    cooking = false;
}

public void startBreak()
{
    //bad chef stops cooking
    cooking = false;
    notifyAll();
    //measure break time of bad chef
    long stopCookingTime = System.currentTimeMillis();
    while(cookingTime > 0 && cooking)
    {
        try 
        {
            wait();
        } catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
    int pausedTime = toIntExact((System.currentTimeMillis() - stopCookingTime)/1000);
    //calculate remaining cookingTime
    cookingTime -= pausedTime;
    cooking = true;
    notifyAll();
}

Maybe someone has the time to read through and shortly outline my misconceptions on monitoring/wait() and notify() on multiple threads, I'd gladly appreciate it!

Sufian
  • 6,405
  • 16
  • 66
  • 120
freeprogramer233
  • 193
  • 2
  • 11
  • 1
    because you can mark volatile a value type or the _reference_ to an object, not the object itself – Giovanni Caporaletti Mar 12 '16 at 12:09
  • That totally makes sense and clears things up. Thanks a lot! – freeprogramer233 Mar 12 '16 at 12:20
  • The volatile keyword provides a weak form of thread safety. It guarantees visibility for 4-byte (and shorter) data types, but does not provide any form of mutual exclusion or atomicity at all. When full control of synchronization is needed, wait() and notify() are the low level facilities that enable it. Most Java programmers will not use wait() or notify() and instead use the Java Concurrency APIs which are more robust and simpler to use. – scottb Mar 12 '16 at 13:07

4 Answers4

4

static means that all objects of a class share that data. How do you think a static field could thus be used to say anything about the status of a specific thread object?

I guess one could get rid of wait/notify; in a way that one thread has to query properties of other threads. But that means "activity": that "waiting" thread has to do polling. Of course, you can't be polling constantly, so you would want it to sleep for certain periods of time. Which is ... almost as waiting, but more complicated, because you have to manage all the subtle details by writing code.

With wait/notify you have a push model. If a thread needs to wait; you tell it to do so; and then, when time comes, it will be woken up. That is a pretty clear, straight forward semantic.

So, when you propose a different model to solve that problem; you would really have to prove that your model achieves the same goal; and beyond that, figure additional benefits of that model.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • Actually I was pretty sure that the "wait"-"notify" approach absoluetly has it's right to exist and just needed some confirmation in order to clearly understand it. Thank you for your answer. – freeprogramer233 Mar 12 '16 at 12:19
3

declaring the relevant objects as static volatile so that other threads learn about the change of state without invoking those methods?

Purpose of static and volatile is completely different from the wait-notify mechanism provided by threads when the state changes or condition is met.

Static - Indicates a field/method is associated at the class rather than instance level and doesn't need an object to be created to use them.

Volatile - Instructs the JVM to always read the latest value of this field, i.e., guarantees visibility of changes to volatile variables across threads and avoids cache coherency issues.

Coming to wait and notify, it is a communication mechanism employed by threads and we will see it from a producer-consumer example.

Producer places the tasks to into queue that needs to be consumed/completed by Consumer.

Start with scenario where a consumer is waiting for a task to appear in the queue (empty queue). Producer puts the tasks and subsequently it notifies any waiting consumer. This wakes up the consumer and it starts processing the tasks. Without that consumer has to keep polling the queue for the task.

From a producer end, it keeps on putting the tasks in the queue untill the queue is full and subsequently producer waits till the space becomes available in the queue. The consumer as it picks up the task and makes some free space in the queue can notify the producer. Without this producer has to poll the queue periodically for free space.

So wait-notify are communication mechanisms that threads use to communicate among themselves of any condition/state changes.

Also with wait and notify, JMM provides memory visibility guarantee with happens before relationship.

On a similar note, JMM guarantees a write to a volatile field happens-before every subsequent read of that field but don't confuse with the wait-notify mechanism provided by threads.

Below is an image from Java revisited link that shows producer-consumer pattern using wait notify. Green dashed lines invoking the notify methods to wake up the waiting threads when the condition is met. You may want to go through the link if interested in the related code.

Java wait-notify with Producer consumer pattern

  • So in your example with a producer and a consumer, where a thread exists for each producer and consumer, how would you declare the queue and it's remaining space/size (variables)? – freeprogramer233 Mar 12 '16 at 12:49
  • @freeprogramer233 in our example queue could be declared as an array and producer and consumer working out of the same array. So consumer waits (empty queue scenario) till there is atleast there is one task to process and producer waits till there is free space in the array (queue full scenario). – Madhusudana Reddy Sunnapu Mar 12 '16 at 12:58
  • @MadhusudanaReddySunnapu Please note that `volatile` also comes with pretty strong visibility guarantees, due to the fact that there is a `happens-before` relationship between a volatile write and a subsequent volatile read of the same variable. – biziclop Mar 12 '16 at 13:58
  • @biziclop Agree as write to a volatile field happens-before every subsequent read of that field and good point to capture in the answer. – Madhusudana Reddy Sunnapu Mar 12 '16 at 14:02
1

Since both are meant for different purpose, let's understand core differences.

static: One copy of variable for all objects.

volatile: Get the value of variable from main memory instead of thread cache

In many applications, you need to have copy for object rather than single copy for all objects. You should not be constrained by using class level copy for multi threaded application with static volatile if your proposal is the way to go to handle multi threaded application.

Now let's come to volatile variable alone in absence of static. Getting latest value from the main memory is fine. But if multiple threads are changing the value, you can't guarantee consistency.

Take an example of your bank account. Assume that your account balance is 50 dollars. One thread is adding the value from deposit() method. Other thread is deducting the value from withdrawal() method.

Thread 1: Got the value of balance as 50 dollars from main memory instead of cache. Deposit happened with 25 dollars and balance has become 75 now. Assume that deposit is not synchronized and no wait() and notify() have been used.

public void deposit(){
     //get balance : 50
     // update : 75
     // print  : 25 ( if withdrawal completes before print statement and 
        after get balance statement  
}

Thread 2: Got the value of balance as 50 dollars from main memory instead of cache. Withdrawal happened for 25 dollars and balance has become 25 now.

public void withdrawal(){
     //get balance : 50
     // update : 25
     // print : 25
}

Just making a variable as volatile does not help. You have to synchronize the data for consistency.

Have a look below SE questions to understand the concepts better:

Difference between volatile and synchronized in Java

Volatile Vs Static in java

Static variable vs Volatile

Community
  • 1
  • 1
Ravindra babu
  • 37,698
  • 11
  • 250
  • 211
1

It may be possible to use locks and/or volatile variables to perform the synchronization tasks that "wait" and "notify" are designed to accomplish, but it would not be efficient. As a simple example of the intended usage, consider a messaging queue and a thread whose job is to read messages and process them. If the thread isn't processing a message and the queue is empty, there won't be anything useful the thread can do.

It would be possible to have a message-processing thread that simply acquired a lock, checked whether the queue was empty, and if not released the lock briefly, then re-acquired it, checked again whether the queue was empty, etc. but such a thread would require that the CPU spend a huge amount of time checking an empty queue for pending messages when there would likely be something else more useful the CPU could be doing.

The simplest way to think of "wait" is as offering an indication to the Java Virtual Machine that a thread has determined that there won't be anything useful for it to do unless or until someone else indicates, via "notify", that something interesting might have happened. The JVM isn't required to do anything with this indication, but in most cases it will prevent the a thread that performs a "wait" from receiving any more CPU time until the object upon which it is waiting receives a "notify". The thread might receive CPU time even before that [e.g. a JVM running in a small embedded system might not bother keeping track of what objects threads were waiting on, but could legitimately have "wait" suspend a thread until the next "notify" is given to any object] but having a thread needlessly receive CPU time a few times, briefly, before it actually needs it would be far less bad than having a thread needlessly receive CPU time continuously.

supercat
  • 77,689
  • 9
  • 166
  • 211