[concurrency-interest] conditional put

Gregg Wonderly gregg at cytetech.com
Tue Nov 6 10:35:06 EST 2007


Tim Peierls wrote:
> On 11/6/07, *kevin bourrillion* <kevinb at google.com 
> <mailto:kevinb at google.com>> wrote:
> 
>     Doug and Bob and I have coincidentally been discussing this again
>     lately; Doug is still skeptical that a truly general-purpose reusable
>     solution is feasible, but I'm a damned optimist.  If we manage to come
>     up with something it will go into the google collections library.

The pattern I use is something like

Value<Type> t, newt;

if( (t = map.putIfAbsent( key, newt = new Value<Type>() ) != null ) {
	newt.destroy();
}

I also put in as much lazy initialization as I can which does not require 
unnecessary synchronization.

I've not had any real issues with doing this.  The primary issue with putting 
anything into a map and the concurrently using it from multiple threads, is that 
the initialization must be atomic. Doing the construction with an local 
reference, I feel, is the best choice.  You could also do something synchronized 
on newt, and perform newt.initialize() instead.

newt = new Value<Type>();
synchronized( newt ) {
	if( ( t = map.putIfAbsent( key, newt ) ) == null ) {
		newt.initialize();
	}
}

but then newt access will need to be synchronized for "read" operations which is 
what doesn't work well for concurrency.  You might also be able to use a 
proxying delegate object to defer initialization, and then have it replace 
itself in the map with the initialized reference so that the first use(s) will 
be synchronized, but will then move away from that.

public interface MyInterface {
	public void func1();
	public int func2();
	public String func3( int v);
	...etc...
}

public class MyProxy implements MyInterface {
	MyInterface del;
	ConcurrentHashMap<Key,Value> map;
	Key k;
	Callable<MyInterface>creator;
	public MyProxy( ConcurrentHashMap<Key,Value> map, Key k,
			Callable<MyInterface>creator, ...other parms... ) {
		this.k = k;
		this.map = map;
		this.creator = creator;
	}

	public synchronized void func1() {
		checkRef();
		del.func1();
	}
	public synchronized int func2() {
		checkRef();
		return del.func2();
	}
	public synchronized String func3( int v ) {
		checkRef();
		return del.func3( v );
	}
	private void checkRef() {
		if( del == null ) {
			del = creator.call( k ...other parms... );
			// Replace the existing entry, this, with
			// the real one we want to use.
			map.put( k, del );
		}
	}
}

This will then quickly create and replace the instance in the map with a real 
reference that can take full advantage of concurrency opportunities. If there is 
not an interface, or a subclassing opportunity, this doesn't work out so well...

Gregg Wonderly


More information about the Concurrency-interest mailing list