Jason S
Jason S

Reputation: 189716

java concurrency: lightweight nonblocking semaphore?

I have a situation where I have a callback that I want to execute once. For the sake of argument let's say it looks like this:

final X once = new X(1);
Runnable r = new Runnable() {
    @Override public void run() {
        if (once.use())
           doSomething();
    }
}

where X is some concurrent object with the following behavior:

I know I can use java.util.concurrent.Semaphore for this, but I don't need the blocking/waiting aspect of it, and I want this to be a one-time use thing.

AtomicInteger doesn't look sufficient unless I do something like

class NTimeUse {
   final private AtomicInteger count;
   public NTimeUse(int N) { this.count = new AtomicInteger(N); }
   public boolean use() {
       while (true)
       {
          int n = this.count.get();
          if (n == 0)
             return false;
          if (this.count.compareAndSet(n, n-1))
             return true;
       }
   }

and I feel queasy about the while loop.

CountDownLatch won't work, because the countDown() method has no return value and can't be executed atomically w/r/t getCount().

Should I just use Semaphore or is there a more appropriate class?

Upvotes: 3

Views: 2980

Answers (3)

user3111822
user3111822

Reputation: 1

// This implements an unfair locking scheme:
while ( mayContinue() ) {
    // acquire the permit and check if it was legally obtained
    if ( counter.decrementAndGet() > 0 )
        return true;
    // return the illegally acquired permit
    counter.incrementAndGet();
}
return false;

Setting the counter back to zero if you discover the permit was illegally obtained creates a race condition when another thread releases a permit. This only works for situations where there are 2 or 3 threads at most. Some other backoff or latching mechanism needs to be added if you have more.

Upvotes: 0

Peter Lawrey
Peter Lawrey

Reputation: 533550

yes. AtomicInteger is non-blocking. You can use getAndDecrement().

You can use something like

if(counter.getAndDecrement() > 0) {
   // something
} else {
   counter.set(0);
}

This will work provided you don't call it two billion times between the decrement and the set. i.e. you would need to have two billion threads stop between these two statements.

Again you can use AtomicLong for extra paranoia.

Upvotes: 1

axtavt
axtavt

Reputation: 242706

In the case of single permit you can use AtomicBoolean:

final AtomicBoolean once = new AtomicBoolean(true);
Runnable r = new Runnable() {
    @Override public void run() {
        if (once.getAndSet(false))
           doSomething();
    }
}

If you need many permits, use your solution with compareAndSet(). Don't worry about the loop, getAndIncrement() works the same way under the cover.

Upvotes: 4

Related Questions