6

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
Alex Poole
  • 183,384
  • 11
  • 179
  • 318
Vik
  • 8,721
  • 27
  • 83
  • 168
  • Every time you call a method on your hasmap, like `get` or `put`, you need to surround it with synchronization. If you have multiple calls in a row, you can surround them all with one synchronization. Post some of your code. – Andrew Williamson Feb 10 '16 at 17:07
  • don's synchronized the map while writing to db. make a copy (in synchronized{}), write the copy to db. – ZhongYu Feb 10 '16 at 17:43
  • what benefit do you see? my map can hold max 50 entries. and i believe making a copy would take the same time as iterating over it. – Vik Feb 10 '16 at 18:21

3 Answers3

3

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.
    }
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • so i am iterating like if(diagMap != null && diagMap.size() > 0){ Set keys = diagMap.keySet(); for (Date key : keys) { String value = diagMap.get(key); dPath.append(key).append(DELIM).append(value); } } wouldn't it release iterator after the for loop? – Vik Feb 10 '16 at 17:18
2

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

Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • i believe it doesn't support overriding removeEldestEntry method to make it behave like a circular queue – Vik Feb 10 '16 at 17:19
  • As i said in my post, I do not have Collections.synchronizedMap available in my env which uses a restricted jdk profile – Vik Feb 10 '16 at 17:27
  • 1
    So I imagine that you can only surround your code with synchronized or use a custom data structure – Davide Lorenzo MARINO Feb 10 '16 at 17:30
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){
    ..
  }
}
Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77