[concurrency-interest] Handling Null Values in ConcurrentHashMap

Holger Hoffstätte holger at wizards.de
Fri May 12 07:22:03 EDT 2006


Tutika Chakravarthy wrote:
> I would like to replace some Hashmaps in our
> application, which are prone to multi threading issues
> with ConCurrentHashMap.

You must understand upfront that this may or may not give you the desired
results, though it will 'likely' 'work' in your case. The
ConcurrentModificationExceptions that you probably see are just a symptom
of a deeper root cause; it is a good idea to fix that instead of taping up
the symptom, simply because it is very likely that you will simply see
other concurrency bugs after this visible problem has been 'fixed'.

The problem with concurrency is not the bugs that you see (like CME); be
thankful for those.

> Currently we keep null key and values in hashmap
> without any issues as HashMap allows them.

Yes, this is a terrible error in the Java Map classes.

> But ConcurrentHashMap does not allow any null key and
> values .
> I would like to know whether anybody is following any
> general practice to tackle this issue .

Make it caller's policy to check for both key and value to be not null.
Include tests for this policy in your unit tests (if you have any).

> It is very difficult to check for null keys and values
> in my entire application .

This is just the price to pay for using the broken HashMap behaviour in
the first place. The "standard" Java libraries are full of these hidden
long-term cost factors. :-(

> I thought of writing a wrapper around
> ConcurrentHashMap which will mask and unmask key and
> values with some other object, when null values are
> getting inserted .
> 
> But the issue is that in certain bulk operations like
> keySet() , values() etc, it is very difficult unmask
> them.

Right. Even then you'd still have the problem that you need to find all
callers of the existing Map constructors and fix them up; this may or may
not be possible, e.g. if you get the Map from somewhere else.

> If anybody has ideas in resolving this kind of issue,
> Please let me know.

You have several options.

1) accept that you have a concurrency problem and fix the root cause, not
just the symptoms by trying to "fix" the Map behaviour; it is just an
indicator that something else is wrong. This may mean a full, partial or
subsystem-limited concurrency analysis of either the whole application or
the affected subsystem (if there are any). This also means that you have
to come up with a stringent definition of what it means for your
application (or the relevant part) to be concurrent. This will expose the
critical sections that you can then address, _for example_ by simply using
a Collections.synchronizedMap() around the original, or by using a
ConcurrentHashMap.

2) invert the above approach and 'invade' all offending code parts with
AOP; this would enable you to fix existing JARs as well. I have attached a
simple AspectJ MapCheck aspect with example that you can weave into your
application. Currently this will throw IllegalArgumentExceptions, but of
course you could modify this by skipping the put operation or using
default values. Please think VERY hard whether this works for your case,
because you may end up replacing values with the default key because a
caller erroneously passed a null key, or vice versa. The existing aspect
was meant to expose the null key/value problems as early as possible.
Skipping the operation may or may not be a viable option in your case.

There is no easy solution/quick fix to your problem.

Holger
-------------- next part --------------

package mapcheck;

import java.util.Map;

public aspect MapNullCheck
{

	pointcut methodsToCheck(Object key, Object value):
	    call(public Object Map.put(Object, Object))
	    && args(key, value)
	    && within(MapAccess);

	Object around(Object key, Object value): methodsToCheck(key, value)
	{
		if (key != null)
		{
			if (value != null)
			{
				return proceed(key, value);
			}
			else
			{
				throw new IllegalArgumentException("no null values!");
			}
		}
		else
		{
			throw new IllegalArgumentException("no null keys!");
		}
	}

}
-------------- next part --------------

package mapcheck;

import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

public class MapAccess
{
    private static Logger log = Logger.getLogger(MapAccess.class);

    public static void main(String[] args)
    {
        Map<String, String> m = new HashMap<String, String>();
        m.put("foo", "bar");
        m.put("keyForNullValue", null);
        m.put(null, "valueForNullKey");
        log.info("map with nulls: " + m);
    }

}


More information about the Concurrency-interest mailing list