Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Atomic compareAndSet but with callback?

Tags:

java

java-8

I know that AtomicReference has compareAndSet, but I feel like what I want to do is this

private final AtomicReference<Boolean> initialized = new AtomicReference<>( false );
...

atomicRef.compareSetAndDo( false, true, () -> {
  // stuff that only happens if false
});

this would probably work too, might be better.

atomicRef.compareAndSet( false, () -> {
  // stuff that only happens if false
  // if I die still false.

   return true;
});

I've noticed there's some new functional constructs but I'm not sure if any of them are what I'm looking for.

Can any of the new constructs do this? if so please provide an example.

update To attempt to simplify my problem, I'm trying to find a less error prone way to guard code in a "do once for object" or (really) lazy initializer fashion, and I know that some developers on my team find compareAndSet confusing.

like image 893
xenoterracide Avatar asked Oct 21 '15 16:10

xenoterracide


3 Answers

guard code in a "do once for object"

how exactly to implement that depends on what you want other threads attempting to execute the same thing in the meantime. if you just let them run past the CAS they may observe things in an intermediate state while the one thread that succeeded does its action.

or (really) lazy initializer fashion

that construct is not thread-safe if you're using it for lazy initializers because the "is initialized" boolean may be set to true by one thread and then execute the block while another thread observes the true-state but reads an empty result.

You can use Atomicreference::updateAndGet if multiple concurrent/repeated initialization attempts are acceptable with one object winning in the end and the others being discarded by GC. The update method should be side-effect-free.

Otherwise you should just use the double checked locking pattern with a variable reference field.

Of course you can always package any of these into a higher order function that returns a Runnable or Supplier which you then assign to a final field.

// ==  FunctionalUtils.java

/** @param mayRunMultipleTimes must be side-effect-free */
public static <T> Supplier<T> instantiateOne(Supplier<T> mayRunMultipleTimes) {
  AtomicReference<T> ref = new AtomicReference<>(null);

  return () -> {
    T val = ref.get(); // fast-path if already initialized
    if(val != null)
      return val;
    return ref.updateAndGet(v -> v == null ? mayRunMultipleTimes.get() : v)
  };

}


// == ClassWithLazyField.java

private final Supplier<Foo> lazyInstanceVal = FunctionalUtils.instantiateOne(() -> new Foo());

public Foo getFoo() {
  lazyInstanceVal.get();
}

You can easily encapsulate various custom control-flow and locking patterns this way. Here are two of my own..

like image 68
the8472 Avatar answered Nov 13 '22 04:11

the8472


compareAndSet returns true if the update was done, and false if the actual value was not equal to the expected value.

So just use

if (ref.compareAndSet(expectedValue, newValue)) { 
    ... 
}

That said, I don't really understand your examples, since you're passing true and false to a method taking object references as argument. And your second example doesn't do the same thing as the first one. If the second is what you want, I think what you're after is

ref.getAndUpdate(value -> {
    if (value.equals(expectedValue)) {
        return someNewValue(value);
    }
    else {
        return value;
    }
});
like image 3
JB Nizet Avatar answered Nov 13 '22 04:11

JB Nizet


You’re over-complicating things. Just because there are now lambda expression, you don’t need to solve everything with lambdas:

private volatile boolean initialized;
…
if(!initialized) synchronized(this) {
    if(!initialized) {
        // stuff to be done exactly once
        initialized=true;
    }
}

The double checked locking might not have a good reputation, but for non-static properties, there are little alternatives.

If you consider multiple threads accessing it concurrently in the uninitialized state and want a guaranty that the action runs only once, and that it has completed, before dependent code is executed, an Atomic… object won’t help you.

There’s only one thread that can successfully perform compareAndSet(false,true), but since failure implies that the flag already has the new value, i.e. is initialized, all other threads will proceed as if the “stuff to be done exactly once” has been done while it might still be running. The alternative would be reading the flag first and conditionally perform the stuff and compareAndSet afterwards, but that allows multiple concurrent executions of “stuff”. This is also what happens with updateAndGet or accumulateAndGet and it’s provided function.

To guaranty exactly one execution before proceeding, threads must get blocked, if the “stuff” is currently executed. The code above does this. Note that once the “stuff” has been done, there will be no locking anymore and the performance characteristics of the volatile read are the same as for the Atomic… read.

The only solution which is simpler in programming, is to use a ConcurrentMap:

private final ConcurrentHashMap<String,Boolean> initialized=new ConcurrentHashMap<>();
…
initialized.computeIfAbsent("dummy", ignore -> {
    // stuff to do exactly once
    return true;
});

It might look a bit oversized, but it provides exactly the required performance characteristics. It will guard the initial computation using synchronized (or well, an implementation dependent exclusion mechanism) but perform a single read with volatile semantics on subsequent queries.

If you want a more lightweight solution, you may stay with the double checked locking shown at the beginning of this answer…

like image 2
Holger Avatar answered Nov 13 '22 04:11

Holger