简体   繁体   中英

JAVA - Concurrency and locks

I need to create a hash map like this:

Map<String, List<Integer>>

and I'm using 2 threads.

Here a sample of the output map:

---------------------
| xxx | a  b  c  d  |
| yyy | a' b' c' d' |
| zzz | A  B  C  D  |
---------------------

Both threads contribute to the creation of an entry; so, for example, thread 1 has the following data set to store:

-----------------------------------
| 1 |  (xxx,a)  (yyy,a')  (zzz,A) |
| 2 |  (xxx,b)  (yyy,b')  (zzz,B) |
-----------------------------------

and thread 2 has:


| 1 |  (xxx,c)  (yyy,c')  (zzz,C) |
| 2 |  (xxx,d)  (yyy,d')  (zzz,D) |
-----------------------------------

So, at the same moment, the two threads may compute the same key: thread 1 (xxx,a) and thread 2 (xxx,c).

What is the best lock/unlock mechanism I could use?

I tried the following one

ReadWriteLock lock = new ReentrantReadWriteLock();

executor.submit(() -> {
   lock.writeLock().lock();
   try {
      sleep(1);
      map.put("foo", "bar");
  } finally {
     lock.writeLock().unlock();
  }

});

but I don't want to lock the entire hash map. I want to create a lock IF AND ONLY IF both threads during the processing should work on the same key.

Thanks

First, you should use a concurrent Map:

Map<String, List<Integer>> map = new ConcurrentHashMap<>();

Second you should populate it with synchronized Lists before accessed by multiple threads, like this:

for (String key : keys) {
    map.put(key, Collections.synchronizedList(new ArrayList<>()));
}

Or you do it just in time like this:

List<Integer> list = map.get(key);
if (list == null) {
    map.putIfAbsent(key, Collections.synchronizedList(new ArrayList<>()));
    list = map.get(key);
}

That's it. Now everything should be thread-safe.

Note: it is important to work with a list not before fetching it from the map. Due to concurrency more than one thread may try to add a missing list to the map, but only one of them will succeed. Hence after trying to add a missing list always fetch it afterwards from the map.

Not sure if the other answer will do what you want because you need to read from the map (to see if the key is already there or not) and then update in an atomic operation.

Not sure if this is the most elegant solution, but you could use something like this...

import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap;

public class KeyLockMap<K, V> {

    private Map<K, V> map;

    private Set<K> lockKeys;

    public KeyLockMap() {
        map = new ConcurrentHashMap<K, V>();
        lockKeys = new HashSet<K>();
    }

    public void lock(K key) throws InterruptedException {
        synchronized(lockKeys) {
            // Keep waiting while the lockKeys set contains the key we want to update
            while(lockKeys.contains(key)) {
                lockKeys.wait();
            }
        }
    }

    public void unlock(K key) {
        synchronized(lockKeys) {
            lockKeys.remove(key);
            // Notify any threads that may be waiting for this key to be removed
            lockKeys.notifyAll();
        }
    }

    public Map<K, V> getMap() {
        return map;
    }
}

Now in your threads, you can do...

keyLockMap.lock(KEY);
Map<String, List<Integer>> map = keyLockMap.getMap();
...do updates...
keyLockMap.unlock(KEY);

Hope this helps.


Another way that may work is to use "putIfAbsent" on a ConcurrentHashMap, for example...

List<Integer> newValue = new CopyOnWriteArrayList<>();
List<Integer> value = concurrentMap.putIfAbsent(KEY, newValue);

if(value == null) {
    value = newValue;
}

value.add(DATA);

If another thread does the putIfAbsent first, you will get the CopyOnWriteArrayList that was created by that thread and you will be able to add data to it because the CopyOnWriteArrayList is thread safe.

(Edited to account for null being returned on the first put...)

There is no need to reinvent the wheel, java.util.concurrent package already contains thread-safe implementation of Map interface - ConcurrentHashMap .

However, you will have to synchronize your List as well (see BretC's comment below). So probably the most convenient solution would be to use Guava synchronized multimap . Like that:

public static void main(String[] args) {

    Multimap <String, Integer> map = Multimaps.synchronizedMultimap(HashMultimap.<String, Integer> create());

    // In Thread 1
    map.put("foo", 1);

    //In Thread 2
    map.put("foo", 2);

    System.out.println(map); // prints {foo=[1, 2]}
}

You should use Hashtable instead of Hashmap. Hashtable is thread safe (already synchronized), not need implement again the lock/unlock mechanism.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM