Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can Boolean flag not also be used as wait() / notifyAll() mutex?

I have a long-running Runnable. It performs a large number of iterations inside a while-loop in its run() function. I need functionality to pause and resume the runnable, which I implemented using a volatile Boolean pauseFlag that can be set by another thread.

Once the Runnable has detected that pauseFlag is true, it calls pauseFlag.wait() to pause its execution. Resuming is done through setting pauseFlag to false and then calling pauseFlag.notifyAll().

So the pauseFlag both acts as a flag and a mutex. This combined functionality does not work, however. The Runnable keeps blocking on pauseFlag.wait() indefinitely.

If I create a separate mutex, say, Object mutex = new Object(); and use mutex.notifyAll() / mutex.wait(), while still using pauseFlag as a boolean flag, the Runnable does behave as intended.

The non-working code is shown below:

public class PausableRunnable implements Runnable
{
    private boolean done;

    private volatile Boolean pauseFlag = false;

    /** Pause execution. This is an asynchronous (non-blocking) call. */
    public void pause() // <-- called by another thread
    {
        pauseFlag = true;
    }

    /** Resume execution */
    public void resume() // <-- called by another thread
    {
        pauseFlag = false;
        synchronized (pauseFlag)
        {
            pauseFlag.notifyAll();
        }
    }

    @Override
    public void run()
    {
        try
        {
            while (!done && !Thread.currentThread().isInterrupted())
            {
                while (pauseFlag)
                {
                    synchronized (pauseFlag)
                    {
                        // Pause flag was set. Suspend until we are notified that we can continue
                        pauseFlag.wait();
                    }
                }
                // execute our main behaviour. set done = true when done iterating.
                // ....
            }
        } catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
    }
}

So, while I have found a solution by using a separate object, I'd like to understand the issue. Why doesn't the above implementation work?

like image 566
tjalling Avatar asked Feb 11 '23 14:02

tjalling


1 Answers

I made this very same mistake once.

wait/notify works on an object, not a reference

When you change the object referred to by

private volatile Boolean pauseFlag

the wait is still referring to the original object. (As pointed out in the comments, there will usually be only two Boolean objects, TRUE and FALSE, making this even harder to debug, because you might get the correct one by chance)

So it's best to use a final reference that never changes its underlying object when using wait/notify.

like image 78
artbristol Avatar answered Feb 13 '23 04:02

artbristol