72

I need a data structure that is a LinkedHashMap and is thread safe.

How can I do that ?

mmcdole
  • 91,488
  • 60
  • 186
  • 222
Peter Lee
  • 1,011
  • 1
  • 10
  • 11
  • 7
    I think the question is underspecified. Why do you say that it needs to be "a LinkedHashMap"? What behavior are you really after? – Kevin Bourrillion Nov 04 '09 at 18:12
  • 1
    Similar question: http://stackoverflow.com/questions/1815646/how-to-implement-concurrenthashmap-with-features-similar-in-linkedhashmap – Vadzim Jan 22 '15 at 12:08

9 Answers9

38

You can wrap the map in a Collections.synchronizedMap to get a synchronized hashmap that maintains insertion order. This is not as efficient as a ConcurrentHashMap (and doesn't implement the extra interface methods of ConcurrentMap) but it does get you the (somewhat) thread safe behavior.

Even the mighty Google Collections doesn't appear to have solved this particular problem yet. However, there is one project that does try to tackle the problem.

I say somewhat on the synchronization, because iteration is still not thread safe in the sense that concurrent modification exceptions can happen.

Yishai
  • 90,445
  • 31
  • 189
  • 263
  • 8
    +1. I just want to add that as I remember it, the reason it isn't there is because in order to preserve the behavior with access order it would more or less be forced into locking the entire table anyway (or have to be over complicated in a way that would risk being either deadlock prone or slow) so doing synchronization around an ordinary LinkedHashMap would in most cases be more or less equally efficient. The explanation might be a whitewash but it makes sense to me. – Fredrik Jan 16 '10 at 22:13
  • 9
    (Disclaimer: I didn't vote nor downvote). Don't like this approach. Synchronized != Concurrent. if you have an intensive parallel job over the map, your n-1 threads will be blocked all the time. While Concurrent should avoid blocking in most situations. – juanmf Feb 15 '17 at 02:08
  • It was also cloned into `org.apache.groovy.util.concurrent.concurrentlinkedhashmap.ConcurrentLinkedHashMap`. – Vadzim Sep 29 '22 at 13:40
17

There's a number of different approaches to this problem. You could use:

Collections.synchronizedMap(new LinkedHashMap());

as the other responses have suggested but this has several gotchas you'll need to be aware of. Most notably is that you will often need to hold the collections synchronized lock when iterating over the collection, which in turn prevents other threads from accessing the collection until you've completed iterating over it. (See Java theory and practice: Concurrent collections classes). For example:

synchronized(map) {
    for (Object obj: map) {
        // Do work here
    }
}

Using

new ConcurrentHashMap();

is probably a better choice as you won't need to lock the collection to iterate over it.

Finally, you might want to consider a more functional programming approach. That is you could consider the map as essentially immutable. Instead of adding to an existing Map, you would create a new one that contains the contents of the old map plus the new addition. This sounds pretty bizarre at first, but it is actually the way Scala deals with concurrency and collections

hohonuuli
  • 1,974
  • 15
  • 15
  • Thanks for pointing out how Scala tackles the problem. It's quite an eye-opener. – Christopher Yang Jul 12 '13 at 02:15
  • 1
    I guess java too uses that approach in CopyOnWriteArrayList where it copies the list after every write, sounds performance extensive but that's how it is I guess. – Rahul Kumar Dec 19 '16 at 07:38
  • 2
    Yes, except that Scala has a much more efficient way to create a copy of a map with an inserted or deleted element. If I remember correctly, it uses a balanced tree, and shares the subtree nodes between the old and new trees where applicable. A Java implementation of this would probably have to copy the entire tree. – Huw Walters Oct 13 '18 at 18:01
13

There is one implementation available under Google code. A quote from their site:

A high performance version of java.util.LinkedHashMap for use as a software cache.

Design

  • A concurrent linked list runs through a ConcurrentHashMap to provide eviction ordering.
  • Supports insertion and access ordered eviction policies (FIFO, LRU, and Second Chance).
Andrey Adamovich
  • 20,285
  • 14
  • 94
  • 132
  • 3
    The main committer for this project is Ben Manes, a google software engineer, so it's pretty trustworthy (IMHO). – Marco Dec 12 '13 at 22:50
  • Can Manes' `ConcurrentLinkedHashMap` be used not as a cache, meaning, can it be used without any eviction? – AlikElzin-kilaka Jan 30 '19 at 08:21
  • `.maximumWeightedCapacity(Long.MAX_VALUE - Integer.MAX_VALUE)`. – AlikElzin-kilaka Jan 30 '19 at 11:09
  • 3
    ConcurrentLinkedHashMap is now Caffeine https://github.com/ben-manes/caffeine – user1767316 Dec 17 '20 at 10:44
  • But there is no `ConcurrentLinkedHashMap` inside the latest Caffeine, unfortunately. And switching to `Cache.asMap()` looks a bit too complicated. See also on insertion order preservation in Caffeine: https://github.com/ben-manes/caffeine/issues/220 – Vadzim Sep 29 '22 at 13:18
  • See also [Can you still use a ConcurrentLinkedHashMap with Caffeine?](https://stackoverflow.com/questions/71562360/can-you-still-use-a-concurrentlinkedhashmap-with-caffeine) – Vadzim Sep 29 '22 at 13:34
9

You can use a ConcurrentSkipListMap, only available in Java SE/EE 6 or later. It is order presevering in that keys are sorted according to their natural ordering. You need to have a Comparator or make the keys Comparable objects. In order to mimik a linked hash map behavior (iteration order is the order in time in which entries were added) I implemented my key objects to always compare to be greater than a given other object unless it is equal (whatever that is for your object). A wrapped synchronized linked hash map did not suffice because as stated in http://www.ibm.com/developerworks/java/library/j-jtp07233.html: "The synchronized collections wrappers, synchronizedMap and synchronizedList, are sometimes called conditionally thread-safe -- all individual operations are thread-safe, but sequences of operations where the control flow depends on the results of previous operations may be subject to data races. The first snippet in Listing 1 shows the common put-if-absent idiom -- if an entry does not already exist in the Map, add it. Unfortunately, as written, it is possible for another thread to insert a value with the same key between the time the containsKey() method returns and the time the put() method is called. If you want to ensure exactly-once insertion, you need to wrap the pair of statements with a synchronized block that synchronizes on the Map m."

So what only helps is a ConcurrentSkipListMap which is 3-5 times slower than a normal ConcurrentHashMap.

Paul
  • 99
  • 1
  • 1
  • 5
    Unfortunately the custom comparator to define insertion order doesn't work. This assumes the first object passed to compare() is the newly inserted one. But the ConcurrentSkipList map also calls compare when iterating (at least with `map.entrySet()`), in which case you don't know which of the 2 objects is the one inserted first. Unless this object contains a creation date - but this fails for the common case of Strings as keys for example. – Alexander Klimetschek Apr 07 '14 at 22:21
4

Collections.synchronizedMap(new LinkedHashMap())

David Crawshaw
  • 10,427
  • 6
  • 37
  • 39
  • 3
    But you'll still need to synchronize manually if you want to do any composite operations like the extra ones offered by ConcurrentHashMap – Adrian Pronk Sep 08 '09 at 21:47
4

Since the ConcurrentHashMap offers a few important extra methods that are not in the Map interface, simply wrapping a LinkedHashMap with a synchronizedMap won't give you the same functionality, in particular, they won't give you anything like the putIfAbsent(), replace(key, oldValue, newValue) and remove(key, oldValue) methods which make the ConcurrentHashMap so useful.

Unless there's some apache library that has implemented what you want, you'll probably have to use a LinkedHashMap and provide suitable synchronized{} blocks of your own.

Adrian Pronk
  • 13,486
  • 7
  • 36
  • 60
  • Hmmm, downvote after six years, and no reason given. It's not because the question changed so that my answer is now answering the wrong question. Ideas? – Adrian Pronk Dec 08 '15 at 23:55
  • 1
    ConcurrentHashMap are sorted, not ordered like LinkedHashMap are and, I believe, can't thus be the required concurent alternative to a LinkedHashMap. – user1767316 Dec 17 '20 at 10:36
1

I just tried synchronized bounded LRU Map based on insertion order LinkedConcurrentHashMap; with Read/Write Lock for synchronization. So when you are using iterator; you have to acquire WriteLock to avoid ConcurrentModificationException.
This is better than Collections.synchronizedMap.

public class LinkedConcurrentHashMap<K, V> {

    private LinkedHashMap<K, V> linkedHashMap = null;
    private final int cacheSize;  
    private ReadWriteLock readWriteLock = null;

    public LinkedConcurrentHashMap(LinkedHashMap<K, V> psCacheMap, int size) {
        this.linkedHashMap  = psCacheMap;
        cacheSize = size;
        readWriteLock=new ReentrantReadWriteLock();
    }

    public void put(K key, V value) throws SQLException{
        Lock writeLock=readWriteLock.writeLock();
        try{
            writeLock.lock();
            if(linkedHashMap.size() >= cacheSize && cacheSize > 0){
                K oldAgedKey = linkedHashMap.keySet().iterator().next();
                remove(oldAgedKey);
            }
            linkedHashMap.put(key, value);
        }finally{
            writeLock.unlock();
        }
    }

    public V get(K key){
        Lock readLock=readWriteLock.readLock();
        try{
            readLock.lock();
            return linkedHashMap.get(key);
        }finally{
            readLock.unlock();
        }
    }

    public boolean containsKey(K key){
        Lock readLock=readWriteLock.readLock();
        try{
            readLock.lock();
            return linkedHashMap.containsKey(key);
        }finally{
            readLock.unlock();
        }
    }

    public V remove(K key){
        Lock writeLock=readWriteLock.writeLock();
        try{
            writeLock.lock();
            return linkedHashMap.remove(key);
        }finally{
            writeLock.unlock();
        }
    }

    public ReadWriteLock getLock(){
        return readWriteLock;
    }

    public Set<Map.Entry<K, V>> entrySet(){
        return linkedHashMap.entrySet();
    }
}
Kanagavelu Sugumar
  • 18,766
  • 20
  • 94
  • 101
  • If you implement `ConcurrentMap`, you would have got synchronization for free (No need of `ReadWriteLock`). – naXa stands with Ukraine Mar 04 '15 at 07:31
  • @naXa I just tried LRU Map, so while the map is full; adding of a new element also should remove lease recently used element, So i am in need lock implementation. – Kanagavelu Sugumar Mar 04 '15 at 08:21
  • And i know that LRUMap can be implemented through LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) using accessOrder boolean value to be true. But which is non synchronized. – Kanagavelu Sugumar Mar 04 '15 at 08:23
  • 1
    I don't think using `readLock` on `get` is thread safe. The `get` method of `LinkedHashMap` also modify its internal linked list in order to move the element to the head. This is not thread safe. – fluency03 Jun 14 '21 at 16:25
0

The answer is pretty much no, there's nothing equivalent to a ConcurrentHashMap that is sorted (like the LinkedHashMap). As other people pointed out, you can wrap your collection using Collections.synchronizedMap(-yourmap-) however this will not give you the same level of fine grained locking. It will simply block the entire map on every operation.

Your best bet is to either use synchronized around any access to the map (where it matters, of course. You may not care about dirty reads, for example) or to write a wrapper around the map that determines when it should or should not lock.

Malaxeur
  • 5,973
  • 1
  • 36
  • 34
  • 1
    If you have unsyncronized reads to a hashmap, the reads might not just be dirty, they may be corrupt, throw exceptions, etc, especially in a multi-CPU environment. – Yishai Sep 08 '09 at 05:00
  • 2
    There's been cases of HashMap going into an infinite loop because of interference by concurrent updates. – Adrian Pronk Sep 08 '09 at 21:49
0

How about this.

Take your favourite open-source concurrent HashMap implementation. Sadly it can't be Java's ConcurrentHashMap as it's basically impossible to copy and modify that due to huge numbers of package-private stuff. (Why do the Java authors always do that?)

Add a ConcurrentLinkedDeque field.

Modify all of the put methods so that if an insertion is successful the Entry is added to the end of the deque. Modify all of the remove methods so that any removed entries are also removed from the deque. Where a put method replaces the existing value, we don't have to do anything to the deque.

Change all iterator/spliterator methods so that they delegate to the deque.

There's no guarantee that the deque and the map have exactly the same contents at all times, but concurrent hash maps don't make those sort of promises anyway.

Removal won't be super fast (have to scan the deque). But most maps are never (or very rarely) asked to remove entries anyway.

You could also achieve this by extending ConcurrentHashMap, or decorating it (decorator pattern).

barneypitt
  • 979
  • 9
  • 11