简体   繁体   中英

Java concurrency: resettable enabled/disabled wait condition

I want to let a thread sleep until a certain condition becomes off. Basically, I need three operations:

  • enable() : enable sleeping mode (do nothing if already enabled)
  • disable() : disable sleeping mode (do nothing if already disabled)
  • await() : wait until sleeping mode becomes disabled (or return immediately if the sleeping mode was already disabled) or the thread becomes interrupted ( InterruptedException is thrown)

With this, thread A calls enable() . Now thread B calls await() and goes to sleep until thread A (or another one) calls disable() . This cycle can be repeated.

I know this can be quite easily done with wait() and notify() , but I am wondering if JDK8 has such functionality built-in?

The closest I could find is was a CountdownLatch(1) , unfortunately the implementation is not resettable.

Basically, I just want to call enable() / disable() and await() , while all concurrency concepts are abstracted in the implementation (though await() should throw InterruptedException , which is unavoidable).

You could use a Semaphor too :

import java.util.concurrent.Semaphore;

public class Switch {

    private Semaphore semaphore = new Semaphore(1);

    public void enable() {
        synchronized(this) {
            semaphore.drainPermits(); // 0
            semaphore.reducePermits(1); // -1 or 0
        }
    }

    public void disable() {
        semaphore.release(2); // 1 or 2
    }

    public void await() throws InterruptedException {
        semaphore.acquire();
        semaphore.release();
    }


}

Another possible implementation of Switch :

public class Switch {
    private final AtomicBoolean state = new AtomicBoolean();

    public void enable() {
        state.set(true);
    }

    public void disable() {
        if (state.compareAndSet(true, false)) {
            synchronized (state) {
                state.notifyAll();
            }
        }
    }

    public void await() throws InterruptedException {
        if (state.get()) {
            synchronized (state) {
                while (state.get()) {
                    state.wait();
                }
            }
        }
    }
}

You could use Condition :

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Switch {
    private final Lock lock = new ReentrantLock();
    private final Condition on = lock.newCondition();
    private final Condition off = lock.newCondition();
    private volatile boolean state = true; 

    public void enable() {
        try {
            lock.lock();
            state = true;
            on.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void disable() {
        try {
            lock.lock();
            state = false;
            off.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void await() {
        try {
            lock.lock();
            while(!state) {
                try {
                    off.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException("waiting interrupted.");
                }
            }
        } finally {
            lock.unlock();
        }
    }
}
enable(): enable sleeping mode (do nothing if already enabled)
disable(): disable sleeping mode (do nothing if already disabled)

do nothing if already enabled (disabled) is a bad design, which can lead to subtle bugs which are hard to reproduce and discover. For example, let sleeping mode is disabled, and one thread calls disable() and the other calls enable() . Depending on which call is made first, the mode will stay enabled or disabled forever. To make execution more deterministic, enabling and disabling must be counted, and the final state will be determined (disabled).

Instead, your threads should exchange tokens which do not mask each other. Besides CountdownLatch , (which effectively is a counter of prohibitions), JDK has CyclicBarrier and Phaser , which are resettable counters of prohibitions, and Semaphore , which is a counter of permissions.

UPDT this implementation may work (I did not tested it):

Phaser p = new Phaser(1);

public void await() {
    p.arriveAndAwaitAdvance();
}

public void enable() {
    p.register();
}

public void disable() {
    p.arriveAndDeregister();
}

N sequential calls to enable() require the same number of disable() to pass the awaiting thread.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM