thread safe LinkedHashMap without Collections.sync

2019-06-04 13:51发布

问题:

I am using a LinkedHashMap and the environment is multi threaded so this structure needs to be thread safe. During specific events I need to read the entire map push to db and clear all.

Most of time only writes happen to this map. This map has a limit 50 entries.

I am using Oracle MAF and it does not have Collections.syncronizedMap available. So, what are things I need to put in synchronized blocks to make sure writing and reading doesn't hit me concurrentModificationException etc

Few requirements:

  1. I need to behave it like a circular queue so Overriding removeEldestEntry method of the LinkedHashMap.
  2. I need to preserve the order

回答1:

So, what are things I need to put in synchronized blocks to make sure writing and reading doesn't hit me concurrentModificationException etc

Everything method call should be in a synchronized block.

The tricky one being the use of an Iterator, as you have to hold the lock for the life of the Iterator. e.g.

// pre Java 5.0 code
synchronized(map) { // the lock has to be held for the whole loop.
    for(Iterator iter = map.entrySet().iterator(); iter.hashNext(); ) {
         Map.Entry entry = iter.next();
         String key = (String) entry.getKey();
         MyType value = (MyType) entry.getValue();
         // do something with key and value.
    }
}


回答2:

Most LinkedHashMap operations require synchronization in a multi-threaded environment, even the ones that look pure like get(key), get(key) actually mutates some internal nodes. The easiest you could do is using Collections.synchronizedMap.

Map<K,V> map = Collections.synchronizedMap(new LinkedHashMap<>());

Now if it is not available, you can easily add it, as it is just a simple decorator around map that synchronize all operation.

class SyncMap<T,U> implements Map<T,U>{
  SyncMap<T,U>(LinkedHashMap<T,U> map){
   ..
  }
  public synchronized U get(T t){
    ..
  }
}


回答3:

If you are using a java version 1.5 or newer you can use java.util.concurrent.ConcurrentHashMap.

This is the most efficient implementation of a Map to use in a multithread environment.

It adds also some method like putIfAbsent very useful for atomic operations on the map.

From java doc:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove). Retrievals reflect the results of the most recently completed update operations holding upon their onset. For aggregate operations such as putAll and clear, concurrent retrievals may reflect insertion or removal of only some entries

So verify is this is the behaviour you expect from your class.


If your map has only 50 records and needs to be used as a circular Queue why you use a Map? Is not better to use one of the Queue implementations?


If you need to use a LinkedHashMap use the following:

Map m = Collections.synchronizedMap(new LinkedHashMap());

From javadoc of LinkedHashMap:

Note that this implementation is not synchronized. If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:

 Map m = Collections.synchronizedMap(new LinkedHashMap(...));

https://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html