Search code examples
javaandroidmultithreadingjava-threadsandroid-thread

Stop current thread if mutual exclusion detected


In my android Application I've got a specific method in one of my MODEL classes and this method always gets called from a separate thread other than UI thread.

The AlarmManger periodically runs this method and also user have the option to run this method from UI. (AlarmManger operation and UI operation call this method from a separate thread other than Main application UI thread).

I just do NOT want to allow to run this method same time from UI and AlarmManger.

Of course I can Synchronise this method and that guarantees one thread execute method at ones. But that's NOT what I want.

I want to throw an exception and stop the second thread (could be UI call or AlarmManager depending on situation) if one already executing the method. So I can inform user that operation cancelled as another running instance (AlarmManger or form UI) was Found.

Can I ask what the best approach for a situation like this. Thanks


Solution

  • I make the following assumptions from your question:

    1. You want to guarantee mutual exclusion on the method, but if there is contention just return (signalling that the method didn't execute), not block the thread.
    2. You need the instance of the model class reusable (e.g. assume that once a thread exits the method, some other thread could call again)

    For this the simplest option could be ReentrantLock::tryLock. Something like this (code is not compiled or tested.)

    class MyClass {
        private final ReentrantLock lock = new ReentrantLock();
        public void theMethod() {
            if(!lock.tryLock()) {
                throw new IllegalStateException("Running already");
            }
            try {
                // do stuff
            } finally {
                lock.unlock(); 
            }
        }
    }
    

    Throwing an exception to signal that the method is being executed means performing flow-control via exceptions, which is not good practise. Unless you have a good reason for keeping an exception, I'd recommend this instead:

    class MyClass {
        private final ReentrantLock lock = new ReentrantLock();
        public boolean theMethod() {
            if(!lock.tryLock()) {
                return false;
            }
            try {
                doStuff();
            } finally {
                lock.unlock(); 
            }
            return true;
        }
    }
    

    The return value signals whether the method could run or not, so the caller can react accordingly.

    If (2) above doesn't apply (that is, you just want to run the method once and ensure that it never gets executed again), then you can simply do this:

    class MyClass {
        private final AtomicBoolean hasExecuted = new AtomicBoolean(false);
        public boolean theMethod() {
            if(!hasExecuted.compareAndSet(false, true) {
                return false; // or throw an exception
            }
            doStuff();
            return true;
        }
    }
    

    I hope that helps.