[concurrency-interest] Initializing a map inside a constructor

Osvaldo Pinali Doederlein osvaldo at visionnaire.com.br
Mon Mar 5 15:39:02 EST 2007


One additional tip here is using an immutable map, provided by 
Collections.unmodifiableMap(). This makes your code safe even if you 
have to expose the map by a getter that returns the map, the key set, an 
iterator to the map or any other object that would normally allow 
clients to mess with the map. The unmodifiableMap() method creates one 
layer of indirection to the original map, but only clients that needs 
read-only access have to pay for that. Example:

public class Company {
     private final Map<String,Dept> depts = new HashMap<String,Dept>();
*     private final Map<String,Dept> deptsRO;
*     public Foo() {
         depts.put("Engineering", new Dept(...));
*         deptsRO = Collections.unmodifiableMap(m);
*     }
     public Set<Employee> (String ofDept) {
         return depts.get(ofDept).allEmployees();
     }
     public Set<Dept> allDepts () {
*         return deptsRO.keySet(s);
*     }
}

Notice how our Company class, internally, uses the normal depts map, 
which pays no extra indirection cost, for all tasks that don't expose 
the map's structure to clients. But in a method that must do that, like 
allDepts() (that we offer perhaps to avoid breaking the Law of Demeter 
in the clients...), we use the unmodifiable wrapper. (An unmodifiable 
map's keySet() will return a Set that's also unmodifiable, and so on.)

I have apps that cache vast amounts of metadata in memory - at init time 
I read tons of XML files and database records, put all that stuff in a 
complex graph of immutable objects and use that data from zillions of 
concurrent transactions without any synchronization. Because some of my 
data structures use collections that often have small number of 
children, empty or single-element collections are not rare, so I also 
use other utilities from collections to squeeze some additional 
performance. I created utility methods like the following to convert a 
collection into an "optimized" one if possible:

public static Map optimizedMap (final Map map) {
    switch (map.size()) {
    case 0:
        return Collections.EMPTY_MAP;
    case 1:
        final Map.Entry entry = 
(Map.Entry)map.entrySet().iterator().next();
        return Collections.singletonMap(entry.getKey(), 
entry.getValue());        
    default:
        return map;
    }
}

This version of the utility doesn't return unmodifiable maps (my data 
structures aren't exposed to untrusted code), it only substitutes the 
original map for a special zero- or single-instance map if possible, 
because the latter consume less memory and have alightly faster 
implementations of most methods (e.g., isEmpty() is a mere "return true" 
for EMPTY_MAP and "return false" for singletonMap()). But a version that 
would always return an unmodifiable map is easy, just add 
Collections.unmodifiableMap() only to the default case, since the other 
cases already return an implementation that is unmodifiable as a bonus - 
no extra indirection cost! Now, add similar utility methods 
unmodifiableSet() and unmodifiableList(), and you can build huge 
in-memory, shared, thread-safe data structures that, at least with 
respect to collection usage, will be optimally safe and efficient. And 
you don't even need j.u.c (the application I talked about is doomed by a 
certain J2EE server to use J2SE 1.4).

*Idea: the Collections.unmodifiableXxx() methods could embed the 
intelligence of my utility: if the received collection is empty or a 
singleton, unmodifiableXxx() could return an instance of the optimized 
empty/singleton class, rather than always creating an instance of the 
CollectionsUnmodifiableXxx class. This optimization only requires that 
**unmodifiableXxx() implement an additional switch structure like the 
code above. The result also avoids memory allocation for empty 
collections. As far as I can see, there is no compatibility issue... 
except for a strawman case like code that expects that for any c1 and 
c2, unmodifiableXxx(c1) != **unmodifiableXxx(c2)** even if 
c1.equals(c2), a requirement that's especially absurd for immutable 
collections.*

A+
Osvaldo

Brian Goetz escreveu:
> This code is thread-safe:
>
> public class Foo {
>      private final Map m = new HashMap();
>
>      public Foo() {
>          m.put("foo", "bar");
>      }
>
>      public isKnown(String s) {
>          return m.containsKey(s);
>      }
>
>      // no other modification to m
> }
>
> and this code is not:
>
> public class Foo {
>      private Map m = new HashMap();
>
>      public Foo() {
>          m.put("foo", "bar");
>      }
>
>      public isKnown(String s) {
>          return m.containsKey(s);
>      }
>
>      // no other modification to m
> }
>
> The difference is the final keyword.  If the map is effectively 
> immutable, and the reference to it is final, and the values are set in 
> the constructor, and the 'this' reference doesn't escape construction, 
> you're OK.
>
> Howard Lewis Ship wrote:
>   
>> I'm seeing some very strange effects in my code under load.  I've been
>> reviewing Brian Goetz's concurrency book, but I have a scenario I
>> frequently use that I have come to suspect:
>>
>> I often have classes with a final field of type Map.
>>
>> I initialize the Map, loading values inside the constructor.  I never
>> change the contents
>> of the Map outside of the constructor.
>>
>> I don't use a guard (i.e. synchronized) to access the map from other code.
>>
>> Is this thread safe, or do I need to synchronize the methods (or wrap
>> the map in a synchronzing wrapper)?
>>
>>     
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest at altair.cs.oswego.edu
> http://altair.cs.oswego.edu/mailman/listinfo/concurrency-interest
>
>   


-- 
-----------------------------------------------------------------------
Osvaldo Pinali Doederlein                   Visionnaire Informática S/A
osvaldo at visionnaire.com.br                http://www.visionnaire.com.br
Arquiteto de Tecnologia                          +55 (41) 337-1000 #223

-------------- next part --------------
An HTML attachment was scrubbed...
URL: /pipermail/attachments/20070305/f7310e5e/attachment-0001.html 


More information about the Concurrency-interest mailing list