Mutually exclusive methods

I tried a couple attempts with higher-level constructs, but nothing quite came to mind. I think this may be an occasion to drop down to the low level APIs:

EDIT: I actually think you're trying to set up a problem which is inherently tricky (see second to last paragraph) and probably not needed (see last paragraph). But that said, here's how it could be done, and I'll leave the color commentary for the end of this answer.

private int someMethod1Invocations = 0;
private int someMethod2Invocations = 0;

public void someMethod1() {
    synchronized(this) {
        // Wait for there to be no someMethod2 invocations -- but
        // don't wait on any someMethod1 invocations.
        // Once all someMethod2s are done, increment someMethod1Invocations
        // to signify that we're running, and proceed
        while (someMethod2Invocations > 0)
            wait();
        someMethod1Invocations++;
    }

    // your code here

    synchronized (this) {
        // We're done with this method, so decrement someMethod1Invocations
        // and wake up any threads that were waiting for that to hit 0.
        someMethod1Invocations--;
        notifyAll();
    }
}

public void someMethod2() {
    // comments are all ditto the above
    synchronized(this) {
        while (someMethod1Invocations > 0)
            wait();
        someMethod2Invocations++;
    }

    // your code here
    synchronized(this) {
        someMethod2Invocations--;
        notifyAll();
    }
}

One glaring problem with the above is that it can lead to thread starvation. For instance, someMethod1() is running (and blocking someMethod2()s), and just as it's about to finish, another thread comes along and invokes someMethod1(). That proceeds just fine, and just as it finishes another thread starts someMethod1(), and so on. In this scenario, someMethod2() will never get a chance to run. That's actually not directly a bug in the above code; it's a problem with your very design needs, one which a good solution should actively work to solve. I think a fair AbstractQueuedSynchronizer could do the trick, though that is an exercise left to the reader. :)

Finally, I can't resist but to interject an opinion: given that ConcurrentHashMap operations are pretty darn quick, you could be better off just putting a single mutex around both methods and just being done with it. So yes, threads will have to queue up to invoke someMethod1(), but each thread will finish its turn (and thus let other threads proceed) extremely quickly. It shouldn't be a problem.


This probably can't work (see comments) - leaving it for information.


One way would be to use Semaphores:

  • one semaphore sem1, with one permit, linked to method1
  • one semaphore sem2, with one permit, linked to method2

when entering method1, try to acquire sem2's permit, and if available release it immediately.

See this post for an implementation example.

Note: in your code, even if ConcurrentMap is thread safe, operation 1 and operation 2 (for example) are not atomic - so it is possible in your scenario to have the following interleaving:

  • Thread 1 runs operation 1
  • Thread 2 runs operation 1
  • Thread 2 runs operation 2
  • Thread 1 runs operation 2

I think this should work

class A {
    Lock lock = new Lock();

    private static class Lock {
        int m1;
        int m2;
    }

    public void someMethod1() throws InterruptedException {
        synchronized (lock) {
            while (lock.m2 > 0) {
                lock.wait();
            }
            lock.m1++;
        }

        // someMethod1 and someMethod2 cannot be here simultaneously

        synchronized (lock) {
            lock.m1--;
            lock.notifyAll();
        }
    }

    public void someMethod2() throws InterruptedException {
        synchronized (lock) {
            while (lock.m1 > 0) {
                lock.wait();
            }
            lock.m2++;
        }

        // someMethod1 and someMethod2 cannot be here simultaneously

        synchronized (lock) {
            lock.m2--;
            lock.notifyAll();
        }
    }
}