3

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:

  • constructor: X(int N) -- allocates N use permits

  • boolean use(): If there is at least 1 use permit, consume one of them and return true. Otherwise return false. This operation is atomic with respect to multiple threads.

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?

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • You linked to `java.util.concurrent.Semaphore` rather than [`java.util.concurrent.CountDownLatch`](http://download.oracle.com/javase/6/docs/api/java/util/concurrent/CountDownLatch.html). – Powerlord Mar 22 '11 at 17:22
  • where? I have two hyperlinks and they look appropriate. – Jason S Mar 22 '11 at 17:23

3 Answers3

4

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.

axtavt
  • 239,438
  • 41
  • 511
  • 482
1

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.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • ...but getAndDecrement() has no floor; I'd have to do a bunch of careful stuff to keep it at zero. – Jason S Mar 22 '11 at 17:22
  • @Jason S, Why do you care if it goes below zero? If you are worried about underclocking you can use AtomicLong. – Peter Lawrey Mar 22 '11 at 17:24
  • I suppose the devil's advocate in me says, what's to prevent `use()` from being called enough that it behaves badly? – Jason S Mar 22 '11 at 17:26
  • In that case use AtomicLong, it would have to called one billion times per second for 292 years to underclock. ;) On a modern computer you will struggle to call it a tenth of that speed. – Peter Lawrey Mar 22 '11 at 17:30
  • from a practical sense, you're absolutely right. From a theoretical sense it still makes me nervous. I don't know why, it just does. – Jason S Mar 22 '11 at 17:38
  • 1
    @Jason S, In that case, the answer is to use the loop as you suggested. This is how it is done for getAndDecrement() anyway. – Peter Lawrey Mar 23 '11 at 08:42
  • The statement "AtomicInteger is non-blocking" is nonsensical. I guess you mean that you don't need an explicit lock for updating an atomic integer. However, the operation itself will only return when the update is done and actually will "block" for this time (meaning it will not return before the operation is fully executed). – jrudolph Jul 28 '13 at 10:26
  • @jrudolph All operations must perform a portion of an operation before it can return. In the case of CAS, it only needs to set the L1 cache so even under you definition it does return before this update is visible to other threads. – Peter Lawrey Jul 28 '13 at 10:31
  • 1
    Yeah, I agree, my (personal) definition of non-blocking seems to much stricter than what it usually is meant to be. – jrudolph Jul 29 '13 at 13:07
  • 1
    @jrudolph My definition of "fast", and "huge" appears to be different from most. I agree that on a low enough scale, a loop on CAS is a blocking operation. This is much lower than most developers think about. – Peter Lawrey Jul 29 '13 at 13:32
0
// 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.