views:

198

answers:

6

Is static initialized unmodifiableCollection.get guaranteed immutable?

For:

static final Map FOO = Collections.unmodifiableMap(new HashMap());

Can multiple threads use method get and not run into problems?

Even through items in FOO cannot be added/removed, what's stopping the get method from manipulating FOO's internal state for caching purposes, etc. If the internal state is modified in any way then FOO can't be used concurrently. If this is the case, where are the true immutable collections in java?

A: 

I would suggest for any threaded operation to use ConcurrentHashMap or HashTable, both are thread-safe.

Chris Kannon
Someone is on a vote down spree, lol.
Chris Kannon
Yeah, using concurrency operations for immutable data is probably a bad idea.
Tom Hawtin - tackline
A: 

There is no true immutable map in Java SDK. All of the suggested Maps by Chris are only thread safe. The unmodifiable Map is not immutable either since if the underlying Map changed there will ConcurrentModificationException as well.

If you want the truly immutable map, use ImmutableMap from Google Collections / Guava.

nanda
A: 

Whether a getter on the returned map happens to twiddle with some internal state is unimportant, as long as the object honors its contract (which is to be a map that cannot be modified). So your question is "barking up the wrong tree".

You are right to be cautious of UnmodifiableMap, in the case where you do not have ownership and control over the map it wraps. For example

Map<String,String> wrapped = new HashMap<String,String>();
wrapped.add("pig","oink");
Map<String,String> wrapper = Collections.unmodifiableMap(wrapped);
System.out.println(wrapper.size());
wrapper.put("cow", "moo"); // throws exception
wrapped.put("cow", "moo");
System.out.println(wrapper.size()); // d'oh!
Jonathan Feinberg
"unimportant" -- not true! It's very important if the class is not thread-safe and two threads are stepping all over each other!
Kevin Bourrillion
Read it again, sir. "As long as it honors its contract."
Jonathan Feinberg
A: 

Actually a good question. Think WeakHashMap - that can change without having a mutation operation called on it. LinkedHashMap in access-order mode is much the same.

The API docs for HashMap state:

Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.)

Presumably that should be if and only if. That means that get does not need to be synchronised if the HashMap is 'effectively immutable'.

Tom Hawtin - tackline
Mr/Ms Downvoter: Anything wrong with my answer, or just revenge?
Tom Hawtin - tackline
+4  A: 

Given the specific example:

static final Map FOO = Collections.unmodifiableMap(new HashMap());

Then FOO will be immutable. It will also never have any elements. Given the more general case of:

static final Map BAR = Collections.unmodifiableMap(getMap());

Then whether or not this is immutable is entirely dependent on whether or not someone else can get to the underlying Map, and what type of Map it is. For example, if it is a LinkedHashMap then the underlying linked list could be modified by access order, and could change by calling get(). The safest way (using non-concurrent classes) to do this would be:

static final Map BAR = Collections.unmodifiableMap(new HashMap(getMap()));

The javadocs for HashMap imply that so long as you make no structural changes to the map, then it is safe to use it concurrently, so this should be safe for any of the accessors that you can use, that is getting the various sets and iterating over them and get() should then be safe.

If you can use the concurrent classes, then you could also do:

static final Map BAR = Collections.unmodifiableMap(new ConcurrentHashMap(getMap());

This will be explicitly safe to use from multiple threads, since ConcurrentHashMap is explicitly multi-thread access safe. The internal state might be mutable, but the externally visible state will not be, and since the class is guaranteed to be threadsafe, we can safely consider it to be externally immutable.

Paul Wagland
What if I, say, want to iterate over `BAR`?
Tom Hawtin - tackline
@Tom: As you mention in your answer, and I now mention in mine ;-), iterating over the map should be as safe as using get.
Paul Wagland
But you are talking about `ConcurrentHashMap` and `Map`s of unspecified implementation.
Tom Hawtin - tackline
ConcurrentHashMap is, by definition, going to be safe to use from multiple threads. And, I am not talking about using unspecified maps, that is why I create a new HashMap to pass into unmodifiableMap(), explicitly so that we know it is a HashMap, and not something else.
Paul Wagland
A: 

At the risk of sounding like I'm on an advertising spree, use the Google Immutable Collections and be done with it.

Carl
Why would you introduce a dependency like that without reason?
Tom Hawtin - tackline
Why jump through hurdles to assure immutability when some one else already has?
Carl
Tom, some people just have a stylistic preference for `public static final Map<Foo, Bar> MAP = ImmutableMap.of(foo1, bar1, foo2, bar2);` over `public static final Map<Foo, Bar> MAP = initMap(); private static Map<Foo, Bar> initMap() { Map<Foo, Bar> map = new HashMap<Foo, Bar>(); map.put(foo1, bar1); map.put(foo2, bar2); return Collections.unmodifiableMap(map); }`.
Kevin Bourrillion