Is an immutable Enummap thread safe?

Asked

Viewed 443 times

13

An immutable map built on top of a EnumMap could be used in multi-thread environment or there is some risk of competition problems?

public enum MyEnum {
    VALUE1, VALUE2, VALUE3, VALUE4;
}

private final Map<MyEnum, BObject> myMap;

public MyClassConstructor() {
    EnumMap<MyEnum , BO> mMap = new EnumMap<>(MyEnum.class);
    mMap.put(MyEnum.VALUE1, new ImmutableBO1()); // imutavel
    mMap.put(MyEnum.VALUE2, new ImmutableBO2());
    // etc 
    myMap = Collections.unmodifiableMap(mMap);
    mMap = null; // ilustrativo, todas referencias a mMap são descartadas 
}

1 answer

14


What is an object Thread-Safe?

It is an object that, in a given context of use, ensures secure access to data shared by several threads no unwanted side effects.

These side effects usually occur when one or more threads try to modify the same data set at the same time.

Let’s assume that we have an object with a counter shared between several threads:

int atual = 0;
public int incrementa() {
    atual++;
    return atual;
}

Suppose now that two threads T1 and T2 call at the same time the method incrementa() and occurs the following sequence of execution:

  1. T1 process executes the line atual++ (total == 1)
  2. Process T2 executes the line atual++ (total == 2)
  3. Process T2 returns the value of total, i.e., 2
  4. Process T1 returns the value of total, i.e., 2

Obviously this will cause an unwanted side effect.

A solution is to synchronize the method or block that handles shared data, for example:

int atual = 0;
public synchronized int incrementa() {
    atual++;
    return atual;
}

In this second example, the two threads would not execute the method incrementa() at the same time on the same object. Only one at a time would enter the block with increment, so this would avoid the side effect.

It is important to note that synchronization in the method is equivalent to a block synchronized(this), which means the lock is done on the object and if there are other synchronized methods, there may be a bottleneck. A more suitable way for synchronization would be:

Integer atual = 0;
public Integer incrementa() {
    synchronized (atual) {
        atual++;
        return atual;
    }
}

In this simple example, the above code does not bring many advantages. But the good practice of limiting the scope of synchronization to the maximum makes fewer unnecessary blockages, which makes a big difference in slightly more complex scenarios.

Observing: the above example is for teaching purposes only. Care must be taken when synchronizing an instance of Integer because in some situations Java uses a cache of integers from -127 to +127. Note that this would not occur if we used new Integer(n), but only methods like valueOf() or getInteger(). A commonly used practice, including in the standard Java libraries, is to create an Object instance and assign it to some class attribute, so you can use this object as a unique semaphore.

And if there are no modifications?

Unmodifiable objects are by nature safe for use in multiple threads, because there is no risk of side effects. See what it says to Wikipedia:

Immutable Objects are often Useful because they are inherently thread-safe.

Translation:

Immutable objects are usually useful because they are inherently thread-safe

Anyway, immutable objects do not need to be synchronized to be safely used in multithreading.

A common Enummap is Thread-safe?

Not. See what the document says (javadoc):

If Multiple threads access an Enum map concurrently, and at least one of the threads modifies the map, it should be Synchronized externally.

Translation:

If multiple threads access an Enum map concurrently, and at least one of them modify the map, it must be synchronized externally.

An unmodifiable Enummap is Thread-safe?

Yes. The answer ends up being obvious after we understand the concepts presented above.

Then there will never be problems with immutable objects?

Depends. As I said at the beginning, it depends on the context of use.

Just to quote an example, suppose an immutable map contains mutable Arraylist type elements. If threads different access and modify these lists, side effects will likely occur.

Also, care must be taken with Java classes that are inherently thread-unsafe, as the class SimpleDateFormat. This class extends the class DateFormat, which has an attribute of the type Calendar, which is changeable. In fact, having attributes like Calendar can be a problem in scenarios multi-threading, since he is changeable.

In short, one should analyze all objects shared by threads, including its contents.

And if I need a map Thread-Safe? I must create a wrapper multi-block synchronized?

Java provides synchronized maps for various tastes. Let’s see some:

  • ConcurrentHashMap: a "new" version of the map synchronized to HashTable, supporting manipulation by several threads, but without blocking the read operations.
  • Collections.synchronizedMap(map): returns a wrapper synchronized over the map, but even read methods are synchronized, resulting in more bottlenecks with respect to the ConcurrentHashMap.

Class references and Wrappers synchronized and immutable

The list of Wrappers synchronized is:

public static <T> Collection<T> synchronizedCollection(Collection<T> c);
public static <T> Set<T> synchronizedSet(Set<T> s);
public static <T> List<T> synchronizedList(List<T> list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);

The list of Wrappers immutable is:

public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c);
public static <T> Set<T> unmodifiableSet(Set<? extends T> s);
public static <T> List<T> unmodifiableList(List<? extends T> list);
public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);
public static <T> SortedSet<T> unmodifiableSortedSet(SortedSet<? extends T> s);
public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m);

The official reference of the returning methods Wrappers immutable and synchronized is here.

Synchronized versions of collection classes are found in the package java.util.concurrent. Here are some substitutes for conventional classes:

  • 2

    Hi Luiz, just to complement the answer, I asked this question because some classes modify their internal state in apparently innocent method calls. In this case competition problems may occur even if there are no obvious state modifications to the caller. This is the case, for example, of SimpleDateFormat (see http://answall.com/questions/114/como-usar-o-simpledateformat-em-ambients-competitors). I opened the source code for EnumMap and saw nothing apparent in the calls to the method get, but, in any case, I decided to check hehehe.

  • 2

    Failed to mention that the Collections.synchronizedMap(map) may be a solution when there are concurrent modifications.

  • @Anthonyaccioly Well remembered, I added an explanation regarding Simpledateformat`.

  • 1

    @Victor Although the question is about an immutable Enummap, it is really important to point to alternatives. I added a section on synchronized and immutable classes that can be used.

  • Note that syncing in a Integer may not be good practice -- or Integer"small" constants from general constants are cached in the JVM (in other words, the object referenced by the field atual, after the construction of the class in which he lives, is a "Singleton", will be shared by all instances of his class). This way, if you have multiple instances of this class and different threads operating in these different instances, you may experience serious performance issues due to thread Contention!

  • @Brunoreis You’re right, in large part. Java caches integers in a range of values and in specific situations. I added a remark in the reply explaining the details.

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.